authority_ecto v0.1.0 Authority.Ecto.Changeset

Convenient authentication-related functions for Ecto.Changesets.

Link to this section Summary

Functions

Hashes the value stored in the source field, and puts the resulting hash in the destination field. The source field will be removed from the changeset

Generates a random token value into the given field if it is nil

Based on the token’s purpose, assign an expiration DateTime in the given field

Validates that a change does not contain consecutive characters such as “abc” or “123”

Validates that a change does not contain repetitive characters such as “aaa” or “111”

Validate that a password field has a confirmation and complies with NIST’s Digital Identity Guidelines

Link to this section Types

Link to this type field()
field() :: atom()

Link to this section Functions

Link to this function put_encrypted_password(changeset, source, destination, algorithm \\ :bcrypt)

Hashes the value stored in the source field, and puts the resulting hash in the destination field. The source field will be removed from the changeset.

By default, the password will be hashed using Comeonin.Bcrypt. See put_encrypted_password/4 to use a different algorithm. Valid options are :bcrypt, :argon2, or :pbkdf2.

Examples

iex> changeset = change(%User{}, %{password: "testing123", password_confirmation: "testing123"})
...> changeset = put_encrypted_password(changeset, :password, :encrypted_password)
...> Comeonin.Bcrypt.checkpw("testing123", get_change(changeset, :encrypted_password))
true

iex> changeset = change(%User{}, %{password: "testing123", password_confirmation: "testing123"})
...> changeset = put_encrypted_password(changeset, :password, :encrypted_password, :argon2)
...> Comeonin.Argon2.checkpw("testing123", get_change(changeset, :encrypted_password))
true
Link to this function put_token(changeset, field)
put_token(Ecto.Changeset.t(), field()) :: Ecto.Changeset.t()

Generates a random token value into the given field if it is nil.

Best when paired with Authority.Ecto.HMAC or Cloak encryption to prevent leaking the tokens if the database is compromised.

Examples

It will set a random value if no value is present:

iex> changeset = %Token{} |> change() |> put_token(:token)
...> is_binary(get_change(changeset, :token))
true

If the field already has a value, it will not be changed:

iex> changeset = %Token{} |> change(token: "existing-value") |> put_token(:token)
...> get_field(changeset, :token)
"existing-value"

iex> changeset = %Token{token: "existing-value"} |> change() |> put_token(:token)
...> get_field(changeset, :token)
"existing-value"
Link to this function put_token_expiration(changeset, expiration_field, purpose_field, config)

Based on the token’s purpose, assign an expiration DateTime in the given field.

The value of the purpose field should correspond to a key in the config list. The following formats are supported:

{n, :days}
{n, :hours}
{n, :minutes}
{n, :seconds}

Examples

iex> changeset = %Token{} |> change(purpose: :recovery)
...> changeset = put_token_expiration(changeset, :expires_at, :purpose, recovery: {24, :hours})
...> expires_at = get_change(changeset, :expires_at)
...> expires_at.__struct__
DateTime
Link to this function validate_nonconsecutive(changeset, field, opts \\ [])
validate_nonconsecutive(Ecto.Changeset.t(), field(), [max: integer(), message: String.t()]) :: Ecto.Changeset.t()

Validates that a change does not contain consecutive characters such as “abc” or “123”.

Options

  • :max - the maximum number of consecutive characters, defaults to 3
  • :message - the message on failure, defaults to contains more than %{max} consecutive characters

Examples

iex> changeset = change(%User{}, %{password: "abc"})
...> changeset = validate_nonconsecutive(changeset, :password)
...> changeset.errors[:password]
{"contains more than %{max} consecutive characters", [validation: :nonconsecutive, max: 3]}

iex> changeset = change(%User{}, %{password: "abc"})
...> changeset = validate_nonconsecutive(changeset, :password, max: 4)
...> changeset.errors[:password]
nil
Link to this function validate_nonrepetitive(changeset, field, opts \\ [])
validate_nonrepetitive(Ecto.Changeset.t(), field(), [max: integer(), message: String.t()]) :: Ecto.Changeset.t()

Validates that a change does not contain repetitive characters such as “aaa” or “111”.

Options

  • :max - the maximum number of repeating characters, defaults to 3
  • :message - the message on failure, defaults to contains more than %{max} repeating characters

Examples

iex> changeset = change(%User{}, %{password: "aaa"})
...> changeset = validate_nonrepetitive(changeset, :password)
...> changeset.errors[:password]
{"contains more than %{max} repeating characters", [validation: :nonrepetitive, max: 3]}

iex> changeset = change(%User{}, %{password: "aaa"})
...> changeset = validate_nonrepetitive(changeset, :password, max: 4)
...> changeset.errors[:password]
nil
Link to this function validate_secure_password(changeset, field)
validate_secure_password(Ecto.Changeset.t(), field()) :: Ecto.Changeset.t()

Validate that a password field has a confirmation and complies with NIST’s Digital Identity Guidelines.

Examples

Must be greater than 8 characters:

iex> changeset = change(%User{}, %{password: "a", password_confirmation: "a"})
...> changeset = validate_secure_password(changeset, :password)
...> changeset.errors[:password]
{"should be at least %{count} character(s)", [count: 8, validation: :length, min: 8]}

Must have a confirmation field:

iex> changeset = cast(%User{}, %{password: "pa$$word"}, [:password])
...> changeset = validate_secure_password(changeset, :password)
...> changeset.errors[:password_confirmation]
{"can't be blank", [validation: :required]}

Must have a matching confirmation field:

iex> changeset = cast(%User{}, %{password: "pa$$word", password_confirmation: "foobar"}, [:password, :password_confirmation])
...> changeset = validate_secure_password(changeset, :password)
...> changeset.errors[:password_confirmation]
{"does not match confirmation", [validation: :confirmation]}

Must not have more than 2 repeating characters (e.g. “aaa” or “111”):

iex> changeset = change(%User{}, %{password: "passsword", password_confirmation: "passsword"})
...> changeset = validate_secure_password(changeset, :password)
...> changeset.errors[:password]
{"contains more than %{max} repeating characters", [validation: :nonrepetitive, max: 3]}

Must not have more than 2 consecutive characters (e.g. “abc” or “123”):

iex> changeset = change(%User{}, %{password: "testing123", password_confirmation: "testing123"})
...> changeset = validate_secure_password(changeset, :password)
...> changeset.errors[:password]
{"contains more than %{max} consecutive characters", [validation: :nonconsecutive, max: 3]}

Must not be one of the 1,000 most common passwords:

iex> changeset = change(%User{}, %{password: "spiderman", password_confirmation: "spiderman"})
...> changeset = validate_secure_password(changeset, :password)
...> changeset.errors[:password]
{"is too common", [validation: :exclusion]}