Current section
Files
Jump to
Current section
Files
lib/maat_feather/users/user.ex
defmodule MaatFeather.Users.User do
use Ecto.Schema
import Ecto.Changeset
alias MaatFeather.Auth.TOTPSecretKey
@primary_key false
@foreign_key_type :string
schema "users" do
field(:email, :string)
field(:password, :string, virtual: true)
field(:password_hash, :string)
field(:address, :string, primary_key: true)
field(:role, :string, default: "user")
field(:username, :string)
field(:provider, :string, default: "maat_feather")
field(:is_verified, :boolean, default: false)
field(:has_mfa, :boolean, default: false)
field(:one_time_token, :string)
field(:secret_key, TOTPSecretKey, autogenerate: true)
field(:private_key, :string, virtual: true)
field(:public_key, :string, virtual: true)
timestamps()
end
@doc false
def changeset(user, attrs) do
user
|> cast(attrs, [
:username,
:provider,
:email,
:password,
:role,
:address,
:password_hash,
:secret_key,
:is_verified,
:has_mfa,
:one_time_token
])
|> validate_required([:username, :email, :role])
|> validate_inclusion(:role, [:user, :admin])
|> validate_format(:email, ~r/\A[^@\s]+@[^@\s]+\z/, message: "is not a valid email")
|> validate_length(:password, min: 6, message: "must be at least 6 characters")
|> validate_length(:username, min: 3, message: "must be at least 3 characters")
|> validate_inclusion(:provider, ["maat_feather", "github"])
|> unique_constraint(:address, message: "address is already taken")
|> unique_constraint(:email, message: "email is already taken")
|> unique_constraint(:username, message: "username is already taken")
|> unique_constraint(:secret_key, message: "secret key is already taken")
|> validate_email()
|> put_params()
end
defp validate_email(%Ecto.Changeset{valid?: true, changes: %{email: email}} = changeset) do
case EmailGuard.check(email) do
:ok ->
changeset
_ ->
add_error(changeset, :email, "email check failed")
end
end
defp validate_email(changeset), do: changeset
defp put_params(%Ecto.Changeset{valid?: true, changes: changes} = changeset) do
private_key = X509.PrivateKey.new_rsa(1024)
public_key = X509.PublicKey.derive(private_key)
private_key = X509.PrivateKey.to_pem(private_key)
public_key = X509.PublicKey.to_pem(public_key)
address = Base.encode16(public_key)
changeset =
changeset
|> put_change(:private_key, private_key)
|> put_change(:public_key, public_key)
|> put_change(:address, address)
|> put_change(:one_time_token, :crypto.strong_rand_bytes(16) |> Base.encode16())
if Map.has_key?(changes, :password),
do: change(changeset, Argon2.add_hash(Map.fetch!(changes, :password))),
else: changeset
end
defp put_params(changeset), do: changeset
end