Voile.Schema.Accounts (Voile v0.1.27)

Copy Markdown View Source

The Accounts context.

Summary

Functions

Updates a user by admin without requiring password. Used for administrative user management where password changes are optional.

Updates a user's password by admin (super admin only). Used for administrative password resets.

Emulates that the email will change without actually changing it in the database.

Changeset for user.

Returns an %Ecto.Changeset{} for changing the user email.

Returns a changeset for tracking onboarding user changes with validations.

Returns an %Ecto.Changeset{} for changing the user password.

Returns an %Ecto.Changeset{} for tracking user changes.

Confirms a user by the given token.

Create a new User.

Creates a user from OAuth provider data (Google, PAuS, etc).

Delete a single user.

Deletes the signed token with the given context.

Delivers the magic link login instructions to the given user.

Delivers the confirmation email instructions to the given user.

Delivers the reset password email to the given user.

Delivers the update email instructions to the given user.

Generates a session token.

Gets a single user by ID (safe version). Returns nil if not found.

Gets a single user.

Gets the user by confirmation token without confirming.

Gets a user by email.

Gets a user by email and password.

Get a user by email or register it it's doesn't exist.

Gets a user by login (email, username, or identifier) and password.

Gets the user with the given magic link token.

Gets the user by reset password token.

Gets the user with the given signed token.

Verifies a session token and returns all auth data needed for cross-app token generation in a single optimized call.

Gets user statistics.

Gets a user by identifier with all library associations preloaded.

Fetches minimal user auth data by user_id with roles preloaded.

Checks if a user has a password set.

Checks if a user is currently manually suspended. Takes into account suspension end dates.

Automatically lifts expired suspensions. Should be called periodically (e.g., via a scheduled job).

Gets all users.

Logs the user in by magic link.

Returns the primary role struct for the given user or nil.

Registers a user.

Resets the user password.

Searches users by username, email, or fullname.

Search users with pagination. Returns {users, total_pages}. Accepts page, per_page, and the same params map as search_users/1.

Checks whether the user is in sudo mode.

Manually suspends a user account with a reason.

Lifts the manual suspension from a user account.

Update an existing user data.

Updates the user email using the given token.

Updates a user's profile during onboarding with required field validations.

Updates the user password.

Updates the user password.

Functions

admin_update_user(user, attrs)

Updates a user by admin without requiring password. Used for administrative user management where password changes are optional.

admin_update_user_password(user, attrs)

Updates a user's password by admin (super admin only). Used for administrative password resets.

apply_user_email(user, password, attrs)

Emulates that the email will change without actually changing it in the database.

Examples

iex> apply_user_email(user, "valid password", %{email: ...})
{:ok, %User{}}

iex> apply_user_email(user, "invalid password", %{email: ...})
{:error, %Ecto.Changeset{}}

change_user(user, attrs \\ %{})

Changeset for user.

change_user_email(user, attrs \\ %{})

Returns an %Ecto.Changeset{} for changing the user email.

Examples

iex> change_user_email(user)
%Ecto.Changeset{data: %User{}}

change_user_onboarding(user, attrs \\ %{})

Returns a changeset for tracking onboarding user changes with validations.

change_user_password(user, attrs \\ %{})

Returns an %Ecto.Changeset{} for changing the user password.

Examples

iex> change_user_password(user)
%Ecto.Changeset{data: %User{}}

change_user_registration(user, attrs \\ %{})

Returns an %Ecto.Changeset{} for tracking user changes.

Examples

iex> change_user_registration(user)
%Ecto.Changeset{data: %User{}}

confirm_user(token)

Confirms a user by the given token.

If the token matches, the user account is marked as confirmed and the token is deleted.

create_user(attrs)

Create a new User.

create_user_from_oauth(attrs)

Creates a user from OAuth provider data (Google, PAuS, etc).

This function handles user creation from various OAuth providers. If the user already exists, it updates their last login information.

Examples

iex> create_user_from_oauth(%{email: "user@example.com", name: "John Doe", auth_provider: "google"})
{:ok, %User{}}

iex> create_user_from_oauth(%{email: "invalid", name: "John"})
{:error, %Ecto.Changeset{}}

delete_user(user)

Delete a single user.

delete_user_session_token(token)

Deletes the signed token with the given context.

deliver_login_instructions(user, magic_link_url_fun)

Delivers the magic link login instructions to the given user.

deliver_user_confirmation_instructions(user, confirmation_url_fun)

Delivers the confirmation email instructions to the given user.

Examples

iex> deliver_user_confirmation_instructions(user, &url(~p"/users/confirm/#{&1}"))
{:ok, %{to: ..., body: ...}}

iex> deliver_user_confirmation_instructions(confirmed_user, &url(~p"/users/confirm/#{&1}"))
{:error, :already_confirmed}

deliver_user_reset_password_instructions(user, reset_password_url_fun)

Delivers the reset password email to the given user.

Examples

iex> deliver_user_reset_password_instructions(user, &url(~p"/users/reset_password/#{&1}"))
{:ok, %{to: ..., body: ...}}

deliver_user_update_email_instructions(user, current_email, update_email_url_fun)

Delivers the update email instructions to the given user.

Examples

iex> deliver_user_update_email_instructions(user, current_email, &url(~p"/users/settings/confirm_email/#{&1}"))
{:ok, %{to: ..., body: ...}}

generate_user_session_token(user)

Generates a session token.

get_user(id)

Gets a single user by ID (safe version). Returns nil if not found.

Examples

iex> get_user(123)
%User{}

iex> get_user(456)
nil

get_user!(id)

Gets a single user.

Raises Ecto.NoResultsError if the User does not exist.

Examples

iex> get_user!(123)
%User{}

iex> get_user!(456)
** (Ecto.NoResultsError)

get_user_by_confirmation_token(token)

Gets the user by confirmation token without confirming.

Examples

iex> get_user_by_confirmation_token("validtoken")
%User{}

iex> get_user_by_confirmation_token("invalidtoken")
nil

get_user_by_email(email)

Gets a user by email.

Examples

iex> get_user_by_email("foo@example.com")
%User{}

iex> get_user_by_email("unknown@example.com")
nil

get_user_by_email_and_password(email, password)

Gets a user by email and password.

Examples

iex> get_user_by_email_and_password("foo@example.com", "correct_password")
%User{}

iex> get_user_by_email_and_password("foo@example.com", "invalid_password")
nil

get_user_by_email_or_register(user)

Get a user by email or register it it's doesn't exist.

get_user_by_identifier(identifier)

get_user_by_login(login)

get_user_by_login_and_password(login, password)

Gets a user by login (email, username, or identifier) and password.

This function attempts to find a user by checking:

  1. Email match
  2. Username match
  3. Identifier match (if the login can be parsed as a number)

Examples

iex> get_user_by_login_and_password("foo@example.com", "correct_password")
%User{}

iex> get_user_by_login_and_password("username", "correct_password")
%User{}

iex> get_user_by_login_and_password("12345", "correct_password")
%User{}

iex> get_user_by_login_and_password("foo@example.com", "invalid_password")
nil

get_user_by_reset_password_token(token)

Gets the user by reset password token.

Examples

iex> get_user_by_reset_password_token("validtoken")
%User{}

iex> get_user_by_reset_password_token("invalidtoken")
nil

get_user_by_session_token(token)

Gets the user with the given signed token.

get_user_session_auth(session_token)

Verifies a session token and returns all auth data needed for cross-app token generation in a single optimized call.

This is the primary function Curatorian should call during session verification. It replaces the pattern of:

  1. get_user_by_session_token/1
  2. get_user_with_roles/1
  3. Voile.Schema.System.get_node_basic/1

Returns

{:ok, %{
  user_id:   binary(),       # UUID string
  node_id:   integer(),
  node_name: String.t(),     # organization display name
  node_abbr: String.t(),     # organization abbreviation
  roles:     [%{name: String.t()}]
}}

{:error, :invalid_token}    # token did not verify
{:error, :user_not_found}   # token valid but user missing (deleted?)
{:error, :node_not_found}   # user exists but node missing (corrupted data?)

Usage in Curatorian

case Voile.Schema.Accounts.get_user_session_auth(session_token) do
  {:ok, auth_info} ->
    cross_app_token = Curatorian.CrossAppToken.sign(auth_info)
    # ...
  {:error, reason} ->
    # redirect to login
end

get_user_statistics()

Gets user statistics.

get_user_with_associations_by_identifier(identifier)

Gets a user by identifier with all library associations preloaded.

get_user_with_roles(user_id)

Fetches minimal user auth data by user_id with roles preloaded.

Returns only the fields needed for cross-app token generation. Does not load any library associations (transactions, fines, reservations, audit_logs, collection_permissions). Significantly lighter than preload_user_assocs/1 for auth-only use cases.

Returns

{:ok, %{
  user_id: binary(),       # UUID
  node_id: integer(),
  roles:   [%{name: String.t()}]
}}
{:error, :not_found}

Usage

case Voile.Schema.Accounts.get_user_with_roles(user.id) do
  {:ok, auth_data} -> # proceed
  {:error, :not_found} -> # handle missing user
end

has_password?(user)

Checks if a user has a password set.

Examples

iex> has_password?(user)
true

iex> has_password?(oauth_user)
false

is_manually_suspended?(arg1)

Checks if a user is currently manually suspended. Takes into account suspension end dates.

Examples

iex> is_manually_suspended?(user)
true

iex> is_manually_suspended?(user_with_expired_suspension)
false

lift_expired_suspensions()

Automatically lifts expired suspensions. Should be called periodically (e.g., via a scheduled job).

Returns the count of users whose suspensions were lifted.

list_users()

Gets all users.

list_users_paginated(page \\ 1, per_page \\ 10, filters \\ %{})

Gets all users with paginated results

login_user_by_magic_link(token)

Logs the user in by magic link.

There are three cases to consider:

  1. The user has already confirmed their email. They are logged in and the magic link is expired.

  2. The user has not confirmed their email and no password is set. In this case, the user gets confirmed, logged in, and all tokens - including session ones - are expired. In theory, no other tokens exist but we delete all of them for best security practices.

  3. The user has not confirmed their email but a password is set. This cannot happen in the default implementation but may be the source of security pitfalls. See the "Mixing magic link and password registration" section of mix help phx.gen.auth.

primary_role(arg1)

Returns the primary role struct for the given user or nil.

Preference order:

  1. first element of preloaded roles list
  2. first role in preloaded user_role_assignments
  3. nil

register_user(attrs)

Registers a user.

Examples

iex> register_user(%{field: value})
{:ok, %User{}}

iex> register_user(%{field: bad_value})
{:error, %Ecto.Changeset{}}

reset_user_password(user, attrs)

Resets the user password.

Examples

iex> reset_user_password(user, %{password: "new long password", password_confirmation: "new long password"})
{:ok, %User{}}

iex> reset_user_password(user, %{password: "valid", password_confirmation: "not the same"})
{:error, %Ecto.Changeset{}}

search_users(query)

Searches users by username, email, or fullname.

Accepts either a plain query string or a map of filters.

Example usages: search_users("alice") search_users(%{"query" => "alice", "node_id" => "1"})

search_users_paginated(page \\ 1, per_page \\ 10, params)

Search users with pagination. Returns {users, total_pages}. Accepts page, per_page, and the same params map as search_users/1.

sudo_mode?(user, minutes \\ -20)

Checks whether the user is in sudo mode.

The user is in sudo mode when the last authentication was done no further than 20 minutes ago. The limit can be given as second argument in minutes.

suspend_user(user, attrs)

Manually suspends a user account with a reason.

Options:

  • :ends_at - DateTime when suspension should automatically expire (optional)
  • :reason - Reason for suspension (required)
  • :suspended_by_id - ID of the admin who suspended the user (required)

Examples

iex> suspend_user(user, %{
  reason: "Violation of terms",
  suspended_by_id: admin_id,
  ends_at: ~U[2025-12-31 23:59:59Z]
})
{:ok, %User{}}

iex> suspend_user(user, %{reason: "", suspended_by_id: admin_id})
{:error, %Ecto.Changeset{}}

unsuspend_user(user)

Lifts the manual suspension from a user account.

Examples

iex> unsuspend_user(user)
{:ok, %User{}}

update_profile_user(user, attrs)

update_user(user, attrs)

Update an existing user data.

update_user_email(user, token)

Updates the user email using the given token.

If the token matches, the user email is updated and the token is deleted. The confirmed_at date is also updated to the current time.

update_user_login(user, attrs)

update_user_onboarding(user, attrs)

Updates a user's profile during onboarding with required field validations.

update_user_password(user, attrs)

Updates the user password.

Returns a tuple with the updated user, as well as a list of expired tokens.

Examples

iex> update_user_password(user, %{password: ...})
{:ok, {%User{}, [...]}}

iex> update_user_password(user, %{password: "too short"})
{:error, %Ecto.Changeset{}}

update_user_password(user, password, attrs)

Updates the user password.

Examples

iex> update_user_password(user, "valid password", %{password: ...})
{:ok, %User{}}

iex> update_user_password(user, "invalid password", %{password: ...})
{:error, %Ecto.Changeset{}}