Current section

Files

Jump to
phoenix priv templates phx.gen.auth context_functions.ex.eex
Raw

priv/templates/phx.gen.auth/context_functions.ex.eex

alias <%= inspect context.module %>.{<%= inspect schema.alias %>, <%= inspect schema.alias %>Token, <%= inspect schema.alias %>Notifier}
## Database getters
@doc """
Gets a <%= schema.singular %> by email.
## Examples
iex> get_<%= schema.singular %>_by_email("foo@example.com")
%<%= inspect schema.alias %>{}
iex> get_<%= schema.singular %>_by_email("unknown@example.com")
nil
"""
def get_<%= schema.singular %>_by_email(email) when is_binary(email) do
Repo.get_by(<%= inspect schema.alias %>, email: email)
end
@doc """
Gets a <%= schema.singular %> by email and password.
## Examples
iex> get_<%= schema.singular %>_by_email_and_password("foo@example.com", "correct_password")
%<%= inspect schema.alias %>{}
iex> get_<%= schema.singular %>_by_email_and_password("foo@example.com", "invalid_password")
nil
"""
def get_<%= schema.singular %>_by_email_and_password(email, password)
when is_binary(email) and is_binary(password) do
<%= schema.singular %> = Repo.get_by(<%= inspect schema.alias %>, email: email)
if <%= inspect schema.alias %>.valid_password?(<%= schema.singular %>, password), do: <%= schema.singular %>
end
@doc """
Gets a single <%= schema.singular %>.
Raises `Ecto.NoResultsError` if the <%= inspect schema.alias %> does not exist.
## Examples
iex> get_<%= schema.singular %>!(123)
%<%= inspect schema.alias %>{}
iex> get_<%= schema.singular %>!(456)
** (Ecto.NoResultsError)
"""
def get_<%= schema.singular %>!(id), do: Repo.get!(<%= inspect schema.alias %>, id)
## <%= schema.human_singular %> registration
@doc """
Registers a <%= schema.singular %>.
## Examples
iex> register_<%= schema.singular %>(%{field: value})
{:ok, %<%= inspect schema.alias %>{}}
iex> register_<%= schema.singular %>(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def register_<%= schema.singular %>(attrs) do
%<%= inspect schema.alias %>{}
|> <%= inspect schema.alias %>.email_changeset(attrs)
|> Repo.insert()
end
## Settings
@doc """
Checks whether the <%= schema.singular %> is in sudo mode.
The <%= schema.singular %> is in sudo mode when the last authentication was done no further
than 20 minutes ago. The limit can be given as second argument in minutes.
"""
def sudo_mode?(<%= schema.singular %>, minutes \\ -20)
def sudo_mode?(%<%= inspect schema.alias %>{authenticated_at: ts}, minutes) when is_struct(ts, <%= inspect datetime_module %>) do
<%= inspect datetime_module %>.after?(ts, <%= inspect datetime_module %>.utc_now() |> <%= inspect datetime_module %>.add(minutes, :minute))
end
def sudo_mode?(_<%= schema.singular %>, _minutes), do: false
@doc """
Returns an `%Ecto.Changeset{}` for changing the <%= schema.singular %> email.
See `<%= inspect context.module %>.<%= inspect schema.alias %>.email_changeset/3` for a list of supported options.
## Examples
iex> change_<%= schema.singular %>_email(<%= schema.singular %>)
%Ecto.Changeset{data: %<%= inspect schema.alias %>{}}
"""
def change_<%= schema.singular %>_email(<%= schema.singular %>, attrs \\ %{}, opts \\ []) do
<%= inspect schema.alias %>.email_changeset(<%= schema.singular %>, attrs, opts)
end
@doc """
Updates the <%= schema.singular %> email using the given token.
If the token matches, the <%= schema.singular %> email is updated and the token is deleted.
"""
def update_<%= schema.singular %>_email(<%= schema.singular %>, token) do
context = "change:#{<%= schema.singular %>.email}"
Repo.transact(fn ->
with {:ok, query} <- <%= inspect schema.alias %>Token.verify_change_email_token_query(token, context),
%<%= inspect schema.alias %>Token{sent_to: email} <- Repo.one(query),
{:ok, <%= schema.singular %>} <- Repo.update(<%= inspect schema.alias %>.email_changeset(<%= schema.singular %>, %{email: email})),
{_count, _result} <-
Repo.delete_all(from(<%= inspect schema.alias %>Token, where: [<%= schema.singular %>_id: ^<%= schema.singular %>.id, context: ^context])) do
{:ok, <%= schema.singular %>}
else
_ -> {:error, :transaction_aborted}
end
end)
end
@doc """
Returns an `%Ecto.Changeset{}` for changing the <%= schema.singular %> password.
See `<%= inspect context.module %>.<%= inspect schema.alias %>.password_changeset/3` for a list of supported options.
## Examples
iex> change_<%= schema.singular %>_password(<%= schema.singular %>)
%Ecto.Changeset{data: %<%= inspect schema.alias %>{}}
"""
def change_<%= schema.singular %>_password(<%= schema.singular %>, attrs \\ %{}, opts \\ []) do
<%= inspect schema.alias %>.password_changeset(<%= schema.singular %>, attrs, opts)
end
@doc """
Updates the <%= schema.singular %> password.
Returns a tuple with the updated <%= schema.singular %>, as well as a list of expired tokens.
## Examples
iex> update_<%= schema.singular %>_password(<%= schema.singular %>, %{password: ...})
{:ok, {%<%= inspect schema.alias %>{}, [...]}}
iex> update_<%= schema.singular %>_password(<%= schema.singular %>, %{password: "too short"})
{:error, %Ecto.Changeset{}}
"""
def update_<%= schema.singular %>_password(<%= schema.singular %>, attrs) do
<%= schema.singular %>
|> <%= inspect schema.alias %>.password_changeset(attrs)
|> update_<%= schema.singular %>_and_delete_all_tokens()
end
## Session
@doc """
Generates a session token.
"""
def generate_<%= schema.singular %>_session_token(<%= schema.singular %>) do
{token, <%= schema.singular %>_token} = <%= inspect schema.alias %>Token.build_session_token(<%= schema.singular %>)
Repo.insert!(<%= schema.singular %>_token)
token
end
@doc """
Gets the <%= schema.singular %> with the given signed token.
If the token is valid `{<%= schema.singular %>, token_inserted_at}` is returned, otherwise `nil` is returned.
"""
def get_<%= schema.singular %>_by_session_token(token) do
{:ok, query} = <%= inspect schema.alias %>Token.verify_session_token_query(token)
Repo.one(query)
end
@doc """
Gets the <%= schema.singular %> with the given magic link token.
"""
def get_<%= schema.singular %>_by_magic_link_token(token) do
with {:ok, query} <- <%= inspect schema.alias %>Token.verify_magic_link_token_query(token),
{<%= schema.singular %>, _token} <- Repo.one(query) do
<%= schema.singular %>
else
_ -> nil
end
end
@doc """
Logs the <%= schema.singular %> in by magic link.
There are three cases to consider:
1. The <%= schema.singular %> has already confirmed their email. They are logged in
and the magic link is expired.
2. The <%= schema.singular %> has not confirmed their email and no password is set.
In this case, the <%= schema.singular %> gets confirmed, logged in, and all tokens -
including session ones - are expired. In theory, no other tokens
exist but we delete all of them for best security practices.
3. The <%= schema.singular %> has not confirmed their email but a password is set.
This cannot happen in the default implementation but may be the
source of security pitfalls. See the "Mixing magic link and password registration" section of
`mix help phx.gen.auth`.
"""
def login_<%= schema.singular %>_by_magic_link(token) do
{:ok, query} = <%= inspect schema.alias %>Token.verify_magic_link_token_query(token)
case Repo.one(query) do
# Prevent session fixation attacks by disallowing magic links for unconfirmed users with password
{%<%= inspect schema.alias %>{confirmed_at: nil, hashed_password: hash}, _token} when not is_nil(hash) ->
raise """
magic link log in is not allowed for unconfirmed users with a password set!
This cannot happen with the default implementation, which indicates that you
might have adapted the code to a different use case. Please make sure to read the
"Mixing magic link and password registration" section of `mix help phx.gen.auth`.
"""
{%<%= inspect schema.alias %>{confirmed_at: nil} = <%= schema.singular %>, _token} ->
<%= schema.singular %>
|> <%= inspect schema.alias %>.confirm_changeset()
|> update_<%= schema.singular %>_and_delete_all_tokens()
{<%= schema.singular %>, token} ->
Repo.delete!(token)
{:ok, {<%= schema.singular %>, []}}
nil ->
{:error, :not_found}
end
end
@doc ~S"""
Delivers the update email instructions to the given <%= schema.singular %>.
## Examples
iex> deliver_<%= schema.singular %>_update_email_instructions(<%= schema.singular %>, current_email, &url(~p"<%= schema.route_prefix %>/settings/confirm-email/#{&1}"))
{:ok, %{to: ..., body: ...}}
"""
def deliver_<%= schema.singular %>_update_email_instructions(%<%= inspect schema.alias %>{} = <%= schema.singular %>, current_email, update_email_url_fun)
when is_function(update_email_url_fun, 1) do
{encoded_token, <%= schema.singular %>_token} = <%= inspect schema.alias %>Token.build_email_token(<%= schema.singular %>, "change:#{current_email}")
Repo.insert!(<%= schema.singular %>_token)
<%= inspect schema.alias %>Notifier.deliver_update_email_instructions(<%= schema.singular %>, update_email_url_fun.(encoded_token))
end
@doc """
Delivers the magic link login instructions to the given <%= schema.singular %>.
"""
def deliver_login_instructions(%<%= inspect schema.alias %>{} = <%= schema.singular %>, magic_link_url_fun)
when is_function(magic_link_url_fun, 1) do
{encoded_token, <%= schema.singular %>_token} = <%= inspect schema.alias %>Token.build_email_token(<%= schema.singular %>, "login")
Repo.insert!(<%= schema.singular %>_token)
<%= inspect schema.alias %>Notifier.deliver_login_instructions(<%= schema.singular %>, magic_link_url_fun.(encoded_token))
end
@doc """
Deletes the signed token with the given context.
"""
def delete_<%= schema.singular %>_session_token(token) do
Repo.delete_all(from(<%= inspect schema.alias %>Token, where: [token: ^token, context: "session"]))
:ok
end
## Token helper
defp update_<%= schema.singular %>_and_delete_all_tokens(changeset) do
Repo.transact(fn ->
with {:ok, <%= schema.singular %>} <- Repo.update(changeset) do
tokens_to_expire = Repo.all_by(<%= inspect schema.alias %>Token, <%= schema.singular %>_id: <%= schema.singular %>.id)
Repo.delete_all(from(t in <%= inspect schema.alias %>Token, where: t.id in ^Enum.map(tokens_to_expire, & &1.id)))
{:ok, {<%= schema.singular %>, tokens_to_expire}}
end
end)
end