Packages
guard
0.14.0
0.19.1
0.19.0
0.18.0
0.17.2
0.17.1
0.17.0
0.16.3
0.16.2
0.16.1
0.16.0
0.15.3
0.15.2
0.15.1
0.15.0
0.14.9
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.9
0.12.8
0.12.7
0.12.6
0.12.5
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
0.11.3
0.11.2
0.11.1
0.11.0
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.0
0.7.1
0.7.0
0.6.1
0.6.0
0.5.0
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
Useful package for dealing with user authentication and signup
Current section
Files
Jump to
Current section
Files
lib/guard/models/user.ex
defmodule Guard.User do
@moduledoc false
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@derive {Jason.Encoder,
only: [
:id,
:username,
:fullname,
:email,
:requested_email,
:attrs,
:inserted_at,
:updated_at,
:mobile,
:requested_mobile
]}
schema "users" do
field(:username, :string)
field(:fullname, :string)
field(:locale, :string)
field(:email, :string)
field(:requested_email, :string)
field(:mobile, :string)
field(:requested_mobile, :string)
field(:password, :string, virtual: true)
field(:enc_password, :string)
field(:perms, :map)
field(:provider, :map)
field(:attrs, :map)
field(:pin, :string, virtual: true)
field(:enc_pin, :string)
field(:pin_expiration, :utc_datetime)
field(:email_pin, :string, virtual: true)
field(:enc_email_pin, :string)
field(:email_pin_expiration, :utc_datetime)
field(:context, :map, virtual: true, default: %{})
timestamps()
end
@required_fields ~w(username)a
@optional_fields ~w(password email enc_password perms requested_email provider fullname locale attrs pin enc_pin pin_expiration
email_pin enc_email_pin email_pin_expiration mobile requested_mobile context)a
@doc """
Creates a changeset based on the `model` and `params`.
If no params are provided, an invalid changeset is returned
with no validation performed.
"""
def changeset(model, params) do
model
|> cast(params, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> validate_format(:email, ~r/@/)
|> validate_format(:requested_email, ~r/@/)
|> validate_length(:password, min: 6)
|> validate_length(:username, min: 1)
|> validate_confirmation(:password, message: "password_mismatch")
# Lowercase email, so we can check for duplicates
|> update_change(:email, &downcase/1)
# Lowercase email, so we can check for duplicates
|> update_change(:requested_email, &downcase/1)
# Lowercase username, so we can check for duplicates
|> update_change(:username, &downcase/1)
# Remove all spaces and leading + from mobile phone
|> update_change(:mobile, &clean_mobile_number/1)
# Remove all spaces and leading + from mobile phone
|> update_change(:requested_mobile, &clean_mobile_number/1)
|> unique_constraint(:email, message: "email_taken")
|> unique_constraint(:mobile, message: "mobile_taken")
|> unique_constraint(:username, message: "username_taken")
|> encrypt_password()
|> encrypt_pin()
|> encrypt_email_pin()
end
def clean_mobile_number(v) do
if v do
v |> String.replace("+", "") |> String.replace(" ", "")
else
v
end
end
def downcase(v) do
if v != nil do
v |> String.trim() |> String.downcase()
else
v
end
end
def hash_password(password) do
Bcrypt.hash_pwd_salt(password)
end
def check_password(user, password) do
Bcrypt.verify_pass(password, user.enc_password)
end
defp validate_pin(user_pin, exp_time, check_pin) do
cond do
!user_pin -> {:error, :no_pin}
exp_time && DateTime.diff(DateTime.utc_now(), exp_time) > 0 -> {:error, :pin_expired}
Bcrypt.verify_pass(check_pin, user_pin) -> :ok
true -> {:error, :wrong_pin}
end
end
def validate_email_pin(user, pin) do
validate_pin(user.enc_email_pin, user.email_pin_expiration, pin)
end
def validate_pin(user, pin) do
validate_pin(user.enc_pin, user.pin_expiration, pin)
end
def check_pin(user, pin) do
case validate_pin(user, pin) do
:ok -> true
_ -> false
end
end
defp encrypt_password(current_changeset) do
case current_changeset do
%Ecto.Changeset{valid?: true, changes: %{password: password}} ->
put_change(current_changeset, :enc_password, hash_password(password))
_ ->
current_changeset
end
end
defp encrypt_pin(current_changeset) do
case current_changeset do
%Ecto.Changeset{valid?: true, changes: %{pin: pin}} ->
put_change(current_changeset, :enc_pin, hash_password(pin))
_ ->
current_changeset
end
end
defp encrypt_email_pin(current_changeset) do
case current_changeset do
%Ecto.Changeset{valid?: true, changes: %{email_pin: pin}} ->
put_change(current_changeset, :enc_email_pin, hash_password(pin))
_ ->
current_changeset
end
end
end