defmodule Accountable do @moduledoc """ Documentation for Accountable. """ @supported_tokens ~w(access reset_password) @spec user_by_credentials(String.t(), String.t()) :: {:ok, struct} def user_by_credentials(email, password) do case repo.get_by!(user_schema, email: email) do %{password_hash: nil} -> {:error, "Authentication disabled"} user -> Argon2.check_pass(user, password) end end def user_by_id(id), do: repo.get(user_schema, id) def create_user(attributes) do attributes |> user_changeset() |> repo.insert() end defp user_changeset(attributes) do attributes = put_password_hash(attributes) user_schema.changeset(user_schema.__struct__, attributes) end @spec token_for_user(struct, String.t()) :: {atom, String.t()} def token_for_user(user, type \\ "access") do with true <- Enum.member?(@supported_tokens, type), {:ok, token, _claims} <- claims(user) do {:ok, token} else _ -> {:error, "Unsupported token type"} end end @spec user_by_token(String.t()) :: {:ok, struct} | {:error, any} def user_by_token(token) do case Accountable.Guardian.resource_from_token(token, %{"typ" => "access"}) do {:ok, user, _claims} -> {:ok, user} err -> err end end def put_password_hash(%{password: password} = attrs) do Map.merge(attrs, Argon2.add_hash(password)) end def put_password_hash(attrs), do: attrs def repo, do: Application.get_env(:accountable, :ecto_repo) def user_schema, do: Application.get_env(:accountable, :user_schema, Accountable.User) def claims(user) do claims_fn = Application.get_env(:accountable, :claims, &default_claims/1) claims_fn.(user) end def default_claims(user) do Accountable.Guardian.encode_and_sign( user, %{ "typ" => "access" } ) end end