Sigra.Account.EmailChange (Sigra v1.20.0)

Copy Markdown View Source

Email change lifecycle: request, confirm, cancel.

Implements the confirm-then-switch pattern (D-01): a verification email is sent to the new address while the old email stays active. The email only switches when the user confirms via the token link.

Security Properties

  • One pending change at a time (D-04): new request cancels existing pending
  • New email reserved via pending_email field (D-05): blocks registration races
  • Token TTL: 24h configurable (D-03)
  • Session invalidation on confirm (D-07): all sessions except current
  • Hook integration: :on_email_change fires at confirmation (D-52)

Summary

Functions

Cancel a pending email change.

Confirm an email change via token.

Request an email change.

Functions

cancel(repo, user, opts)

(since 0.8.0)
@spec cancel(module(), map(), keyword()) ::
  {:ok, map()} | {:error, Ecto.Changeset.t()}

Cancel a pending email change.

Clears the pending_email field and deletes any pending change tokens.

Options

  • :changeset_fn - (user, attrs -> Ecto.Changeset.t()) for clearing pending_email
  • :token_query_fn - (user, contexts -> Ecto.Queryable.t()) for token cleanup queries

Returns

  • {:ok, user} on success
  • {:error, changeset} on failure

confirm(repo, encoded_token, opts)

(since 0.8.0)
@spec confirm(module(), String.t(), keyword()) :: {:ok, map()} | :error

Confirm an email change via token.

Verifies the token, switches the user's email to pending_email, clears pending_email, invalidates sessions except current, and runs the :on_email_change hook.

Options

  • :find_user_by_token_fn - (repo, encoded_token -> user | nil) to look up user by change token

  • :changeset_fn - (user, attrs -> Ecto.Changeset.t()) for updating user
  • :token_query_fn - (user, contexts -> Ecto.Queryable.t()) for token cleanup queries
  • :session_store - SessionStore for session invalidation
  • :config - Optional config for hooks
  • :except_token - Current session token to preserve

Returns

  • {:ok, user} on success
  • :error for invalid or expired token

request(repo, user, new_email, opts)

(since 0.8.0)
@spec request(module(), map(), String.t(), keyword()) ::
  {:ok, map(), String.t()}
  | {:error, :same_email | :email_taken | Ecto.Changeset.t()}

Request an email change.

Validates the new email is not the current email and not already taken, then creates a pending email change token.

Options

  • :changeset_fn - (user, attrs -> Ecto.Changeset.t()) for updating pending_email
  • :build_email_token_fn - (user, context -> {encoded_token, token_struct}) to create token
  • :token_query_fn - (user, contexts -> Ecto.Queryable.t()) for token cleanup queries
  • :email_taken_fn - (repo, email -> boolean()) to check email uniqueness
  • :config - Optional config for hooks

Returns

  • {:ok, user, encoded_token} on success
  • {:error, :same_email} if new email matches current
  • {:error, :email_taken} if email is already in use
  • {:error, changeset} on validation failure