lib/ident/factor/model.ex

defmodule Rivet.Ident.Factor do
  @moduledoc """
  Schema for representing and working with an AuthFactor.
  """
  use TypedEctoSchema
  alias Rivet.Ident
  use Rivet.Ecto.Model, export_json: [:name, :expires_at, :value, :details]
  import EctoEnum

  defenum(Types,
    unknown: 0,
    password: 1,
    federated: 2,
    valtok: 3,
    apikey: 4,
    proxy: 5,
    access: 6
  )

  defenum(FederatedTypes,
    none: 0,
    google: 1,
    linkedin: 2,
    facebook: 3,
    twitter: 4,
    twitch: 5
  )

  typed_schema "ident_factors" do
    # identity vs authentication
    field(:type, Types)
    field(:fedtype, FederatedTypes, default: :none)
    field(:name, :string)
    field(:expires_at, :integer)
    field(:value, :string)
    field(:details, Rivet.Utils.Ecto.AtomKeymap)
    # we put these back as passwords
    field(:password, :string, virtual: true)
    # we don't store these again
    field(:secret, :string, virtual: true)
    field(:hash, :string)
    timestamps()
    belongs_to(:user, Ident.User, type: :binary_id, foreign_key: :user_id)
  end

  @required_fields [:type, :expires_at, :user_id]
  @update_fields [:fedtype, :name, :value, :details, :password]
  use Rivet.Ecto.Collection, not_found: :atom, required: @required_fields, update: @update_fields

  @impl Rivet.Ecto.Collection
  def build(params) do
    %__MODULE__{}
    |> cast(params, @required_fields ++ @update_fields)
    |> validate()
    |> hash_password()
  end

  def hash_password(changeset = %Ecto.Changeset{valid?: true, changes: %{password: pass}}) do
    put_change(changeset, :hash, Ident.Factor.Password.hash(pass))
    # consider: delete password: token?
  end

  def hash_password(changeset), do: changeset
end