Sigra.PasswordPolicy (Sigra v1.20.0)

Copy Markdown View Source

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)"]}

Summary

Functions

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

Assesses password strength for UI feedback.

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

Functions

check_breached(password)

(since 0.2.0)
@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(password)

(since 0.2.0)
@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(changeset, opts \\ [])

(since 0.2.0)
@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)