# `Sigra.Account.EmailChange`
[🔗](https://github.com/sztheory/sigra/blob/v1.20.0/lib/sigra/account/email_change.ex#L1)

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)

# `cancel`
*since 0.8.0* 

```elixir
@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`
*since 0.8.0* 

```elixir
@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`
*since 0.8.0* 

```elixir
@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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
