# `Sigra.PasswordPolicy`
[🔗](https://github.com/sztheory/sigra/blob/v1.20.0/lib/sigra/password_policy.ex#L1)

NIST-compliant password validation and strength analysis.

Validates passwords against configurable policy rules and provides
strength assessment for UI feedback. Follows NIST SP 800-63B guidelines:

- Minimum 8 characters (configurable)
- Maximum 72 bytes (bcrypt compatibility)
- Common password rejection via embedded 10k list
- Optional composition rules (uppercase, digit, special char)
- Optional HIBP breached password check

## Usage with Ecto Changesets

    changeset
    |> Sigra.PasswordPolicy.validate()

    changeset
    |> Sigra.PasswordPolicy.validate(min_length: 12, require_uppercase: true)

## Strength Assessment

    Sigra.PasswordPolicy.check_strength("correcthorsebatterystaple")
    #=> {:strong, []}

    Sigra.PasswordPolicy.check_strength("abc")
    #=> {:weak, ["Use a longer password (at least 8 characters)"]}

# `check_breached`
*since 0.2.0* 

```elixir
@spec check_breached(String.t()) :: {:ok, non_neg_integer()} | {:error, term()}
```

Checks if a password has been found in data breaches via the HIBP API.

Uses the k-Anonymity model: only the first 5 characters of the SHA-1 hash
are sent to the API, so the full password hash is never transmitted.

Returns `{:ok, count}` where count is the number of times the password
appeared in breaches, or `{:error, reason}` on failure.

Off by default -- must be explicitly enabled.

## Examples

    Sigra.PasswordPolicy.check_breached("password")
    #=> {:ok, 3861493}

    Sigra.PasswordPolicy.check_breached("xK9#mP2$vL5")
    #=> {:ok, 0}

# `check_strength`
*since 0.2.0* 

```elixir
@spec check_strength(String.t()) :: {:weak | :fair | :strong, [String.t()]}
```

Assesses password strength for UI feedback.

Returns `{strength, suggestions}` where `strength` is `:weak`, `:fair`,
or `:strong`, and `suggestions` is a list of improvement hints.

## Scoring

- Length: 0-4 chars = 0pts, 5-7 = 1pt, 8-11 = 2pts, 12-15 = 3pts, 16+ = 4pts
- +1 for mixed case
- +1 for digits
- +1 for special characters
- -2 for common password
- -1 for >3 repeated consecutive characters
- -1 for >3 sequential characters

Thresholds: 0-2 = :weak, 3-4 = :fair, 5+ = :strong

## Examples

    iex> Sigra.PasswordPolicy.check_strength("correcthorsebatterystaple")
    {:strong, []}

# `validate`
*since 0.2.0* 

```elixir
@spec validate(
  Ecto.Changeset.t(),
  keyword()
) :: Ecto.Changeset.t()
```

Validates a password field on an Ecto changeset against the configured policy.

If the changeset has no `:password` change, returns the changeset unchanged.

## Options

All options from `@default_opts` can be overridden:

- `:min_length` - Minimum password length (default: 8)
- `:max_bytes` - Maximum password byte size (default: 72)
- `:require_uppercase` - Require at least one uppercase letter (default: false)
- `:require_digit` - Require at least one digit (default: false)
- `:require_special` - Require at least one special character (default: false)
- `:check_common` - Check against common passwords list (default: true)
- `:check_breached` - Check against HIBP API (default: false)

## Examples

    changeset |> Sigra.PasswordPolicy.validate()
    changeset |> Sigra.PasswordPolicy.validate(min_length: 12)

---

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