Packages

sugar for password checking, hashing, and validating

Current section

Files

Jump to
struttura_password lib password.ex
Raw

lib/password.ex

defmodule Password do
@moduledoc """
Documentation for `Password`.
"""
require Ecto.Changeset
import Ecto.Changeset
@invalid_password_error "Password is too short, must be at least 12 characters, with at least one: upper and lower-case letter, a symbol, and a number"
def invalid_password_error(), do: @invalid_password_error
def hash(password) do
Argon2.Base.hash_password(password, Argon2.gen_salt())
end
def check(hash, password) when is_binary(hash) do
%{password_hash: hash}
|> check(password)
end
def check(%{password_hash: _} = map, password) do
Argon2.check_pass(map, password)
|> case do
{:ok, _} -> true
_ -> false
end
end
def validate_password_format(%Ecto.Changeset{} = changeset) do
validate_change(changeset, :password, fn _, value ->
case is_strong_password?(value) do
true ->
[]
false ->
[
{:password,
"""
Invalid password format.
#{@invalid_password_error}
"""}
]
end
end)
end
def put_password_hash(%Ecto.Changeset{} = changeset) do
case changeset do
%Ecto.Changeset{valid?: true, changes: %{password: pass}} ->
put_change(
changeset,
:password_hash,
hash(pass)
)
_ ->
changeset
end
end
def is_strong_password?(password) do
password
|> is_long?()
|> has_regex?(~r/[[:punct:]]/)
|> has_regex?(~r/[[:lower:]]/)
|> has_regex?(~r/[[:upper:]]/)
|> has_regex?(~r/[[:digit:]]/)
end
defp is_long?(password) do
if String.length(password) >= 12 do
password
else
false
end
end
defp has_regex?(false, _), do: false
defp has_regex?(password, regex) do
if Regex.match?(regex, password) do
password
else
false
end
end
end