# Guide: Customizing PhxAuthPlus

This guide covers common customizations after generating authentication with
`mix phx_auth_plus.gen.auth`.

## Table of Contents

- [Adding Custom Fields to User](#adding-custom-fields-to-user)
- [Role-Based Authorization](#role-based-authorization)
- [Configuring the Mailer](#configuring-the-mailer)
- [Changing Password Requirements](#changing-password-requirements)
- [Disabling Account Confirmation](#disabling-account-confirmation)
- [Using API Tokens](#using-api-tokens)
- [Switching Hashing Libraries](#switching-hashing-libraries)
- [Umbrella Project Support](#umbrella-project-support)

---

## Adding Custom Fields to User

The generated `User` schema has `email`, `hashed_password`, and `confirmed_at`.
To add custom fields (e.g., `first_name`, `role`):

### 1. Add fields to the schema

```elixir
# lib/my_app/user.ex
schema "users" do
  field :email, :string
  field :first_name, :string
  field :last_name, :string
  field :role, :string, default: "user"
  field :password, :string, virtual: true, redact: true
  field :hashed_password, :string, redact: true
  field :current_password, :string, virtual: true, redact: true
  field :confirmed_at, :naive_datetime
  timestamps(type: :naive_datetime)
end
```

### 2. Add fields to the registration changeset

```elixir
# lib/my_app/user.ex
def registration_changeset(user, attrs, opts \\ []) do
  user
  |> cast(attrs, [:email, :password, :first_name, :last_name])
  |> validate_required([:email, :password, :first_name, :last_name])
  |> validate_email()
  |> validate_password(opts)
  |> maybe_hash_password(opts)
end
```

### 3. Add fields to the migration

```elixir
# priv/repo/migrations/*_create_users_auth_tables.exs
create table(:users) do
  add :email, :string, null: false
  add :first_name, :string
  add :last_name, :string
  add :role, :string, default: "user"
  # hashed_password is nullable to support magic link users without a password
  add :hashed_password, :string
  add :confirmed_at, :naive_datetime
  timestamps(type: :naive_datetime)
end
```

### 4. Add fields to the registration form

```heex
<!-- lib/my_app_web/user_registration_live.ex -->
<.input field={@form[:first_name]} type="text" label="First name" />
<.input field={@form[:last_name]} type="text" label="Last name" />
```

---

## Role-Based Authorization

### 1. Add a `role` field (see above)

### 2. Create an authorization module

```elixir
# lib/my_app/accounts/authorization.ex
defmodule MyApp.Accounts.Authorization do
  alias MyApp.User

  def admin?(%User{role: "admin"}), do: true
  def admin?(_), do: false

  def can?(%User{role: "admin"}, _), do: true
  def can?(%User{role: "user"}, :read), do: true
  def can?(_, :read), do: true
  def can?(_, _), do: false
end
```

### 3. Create a require_admin plug

```elixir
# lib/my_app_web/user_auth.ex
def require_admin_user(conn, _opts) do
  if conn.assigns.current_scope && conn.assigns.current_scope.user.role == "admin" do
    conn
  else
    conn
    |> put_flash(:error, "You must be an admin to access this page.")
    |> redirect(to: ~p"/")
    |> halt()
  end
end
```

### 4. Use in router

```elixir
pipeline :admin do
  plug :require_authenticated_user
  plug :require_admin_user
end

scope "/admin", MyAppWeb do
  pipe_through [:browser, :admin]
  # admin routes here
end
```

---

## Configuring the Mailer

The generated `UserNotifier` uses Swoosh to send emails. By default in dev,
emails are captured by the local adapter and visible at `/dev/mailbox`.

### Production with SMTP

```elixir
# config/runtime.exs
if config_env() == :prod do
  config :my_app, MyApp.Mailer,
    adapter: Swoosh.Adapters.SMTP,
    relay: "smtp.example.com",
    username: "noreply@example.com",
    password: System.get_env("SMTP_PASSWORD"),
    port: 587,
    tls: :always
end
```

### Other adapters

Swoosh supports many providers: SendGrid, Mailgun, Postmark, SES, etc.
See [Swoosh adapters](https://hexdocs.pm/swoosh/Swoosh.html#module-adapters).

---

## Changing Password Requirements

The generated `password_changeset` validates:
- Minimum 12 characters
- Maximum 72 characters (bcrypt limit)
- At least one lowercase, one uppercase, and one special character or number

These are commented out by default (as in Phoenix 1.8.9). To enforce them:

```elixir
# lib/my_app/user.ex
defp validate_password(changeset, opts) do
  changeset
  |> validate_required([:password])
  |> validate_length(:password, min: 12, max: 72)
  # Uncomment to enforce complexity:
  |> validate_format(:password, ~r/[a-z]/, message: "at least one lower case character")
  |> validate_format(:password, ~r/[A-Z]/, message: "at least one upper case character")
  |> validate_format(:password, ~r/[!?@#$%^&*_0-9]/, message: "at least one digit or punctuation character")
  |> maybe_hash_password(opts)
end
```

---

## Disabling Account Confirmation

If you don't want email confirmation:

1. Remove the `confirmed_at` field from the schema and migration
2. Remove the confirmation routes from the router
3. Remove `confirm_user/1` and `deliver_user_confirmation_instructions/2` from Accounts
4. Remove `UserConfirmationLive`
5. Remove the `:confirm` token context from `UserToken`

Or simply: generate with confirmation, then remove what you don't need.
The confirmation flow is isolated enough to remove cleanly.

---

## Using API Tokens

PhxAuthPlus generates session-based auth for browsers. For API tokens,
you can extend the `UserToken` schema:

```elixir
# lib/my_app/user_token.ex
def build_api_token(user) do
  {token, user_token} = UserToken.build_hashed_token(user, "api")
  Repo.insert!(user_token)
  token
end

def verify_api_token_query(token) do
  case UserToken.verify_hashed_token_query(token, "api") do
    {:ok, query} -> {:ok, query}
    :error -> :error
  end
end
```

```elixir
# lib/my_app_web/api_auth.ex
def authenticate_api(conn, _opts) do
  with ["Bearer " <> token] <- get_req_header(conn, "authorization"),
       {:ok, user} <- Accounts.get_user_by_api_token(token) do
    assign(conn, :current_scope, %MyApp.Scope{user: user})
  else
    _ -> conn |> put_status(:unauthorized) |> json(%{error: "unauthorized"}) |> halt()
  end
end
```

---

## Switching Hashing Libraries

You can switch at generation time:

```bash
mix phx_auth_plus.gen.auth Accounts User users --hashing-lib argon2
```

To switch after generation, change the hashing module calls in:
- `lib/my_app/user.ex` (`hash_pwd_salt`, `verify_pass`, `no_user_verify`)
- Update the dependency in `mix.exs`

---

## Umbrella Project Support

For umbrella projects, use `--context-app`:

```bash
mix phx_auth_plus.gen.auth Accounts User users --context-app my_app_core
```

This generates the context and schemas in `my_app_core` and the web files
in the current web app.
