defmodule Accountable do @moduledoc """ Documentation for Accountable. """ @supported_tokens ~w(access reset_password) @user_schema Application.get_env(:accountable, :user_schema, Accountable.User) @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} <- Accountable.Guardian.encode_and_sign(user, claims) 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 claims() do %{ "typ" => "access", "https://hasura.io/jwt/claims": %{ "x-hasura-allowed-roles": ["user"], "x-hasura-default-role": "user", "x-hasura-user-id": "1234567890", "x-hasura-org-id": "123", "x-hasura-custom": "custom-value" } } end end