Current section

Files

Jump to
pow lib extensions reset_password plug.ex
Raw

lib/extensions/reset_password/plug.ex

defmodule PowResetPassword.Plug do
@moduledoc """
Plug helper methods.
"""
alias Plug.Conn
alias Pow.{Config, Plug, Store.Backend.EtsCache, UUID}
alias PowResetPassword.Ecto.Context, as: ResetPasswordContext
alias PowResetPassword.{Ecto.Schema, Store.ResetTokenCache}
@doc """
Creates a changeset from the user fetched in the connection.
"""
@spec change_user(Conn.t(), map()) :: map()
def change_user(conn, params \\ %{}) do
user = reset_password_user(conn) || user_struct(conn)
Schema.reset_password_changeset(user, params)
end
defp user_struct(conn) do
conn
|> Plug.fetch_config()
|> Config.user!()
|> struct()
end
@doc """
Assigns a `:reset_password_user` key with the user in the connection.
"""
@spec assign_reset_password_user(Conn.t(), map()) :: Conn.t()
def assign_reset_password_user(conn, user) do
Conn.assign(conn, :reset_password_user, user)
end
@doc """
Finds a user for the provided params, creates a token, and stores the user
for the token.
To prevent timing attacks, `Pow.UUID.generate/0` is called whether the user
exists or not.
`:reset_password_token_store` can be passed in the config for the conn. This
value defaults to
`{PowResetPassword.Store.ResetTokenCache, backend: Pow.Store.Backend.EtsCache}`.
The `Pow.Store.Backend.EtsCache` backend store can be changed with the
`:cache_store_backend` option.
"""
@spec create_reset_token(Conn.t(), map()) :: {:ok, map(), Conn.t()} | {:error, map(), Conn.t()}
def create_reset_token(conn, params) do
config = Plug.fetch_config(conn)
token = UUID.generate()
user =
params
|> Map.get("email")
|> ResetPasswordContext.get_by_email(config)
maybe_store_reset_token(conn, user, token, config)
end
defp maybe_store_reset_token(conn, nil, _token, _config) do
changeset = change_user(conn)
{:error, %{changeset | action: :update}, conn}
end
defp maybe_store_reset_token(conn, user, token, config) do
{store, store_config} = store(config)
store.put(store_config, token, user)
{:ok, %{token: token, user: user}, conn}
end
@doc """
Fetches user from the store by the provided token.
See `create_reset_token/2` for more on `:reset_password_token_store` config
option.
"""
@spec user_from_token(Conn.t(), binary()) :: map() | nil
def user_from_token(conn, token) do
{store, store_config} =
conn
|> Plug.fetch_config()
|> store()
store_config
|> store.get(token)
|> case do
:not_found -> nil
user -> user
end
end
@doc """
Updates the password for the user fetched in the connection.
See `create_reset_token/2` for more on `:reset_password_token_store` config
option.
"""
@spec update_user_password(Conn.t(), map()) :: {:ok, map(), Conn.t()} | {:error, map(), Conn.t()}
def update_user_password(conn, params) do
config = Plug.fetch_config(conn)
token = conn.params["id"]
conn
|> reset_password_user()
|> ResetPasswordContext.update_password(params, config)
|> maybe_expire_token(conn, token, config)
end
defp maybe_expire_token({:ok, user}, conn, token, config) do
expire_token(token, config)
{:ok, user, conn}
end
defp maybe_expire_token({:error, changeset}, conn, _token, _config) do
{:error, changeset, conn}
end
defp expire_token(token, config) do
{store, store_config} = store(config)
store.delete(store_config, token)
end
defp reset_password_user(conn) do
conn.assigns[:reset_password_user]
end
defp store(config) do
case Config.get(config, :reset_password_token_store, default_store(config)) do
{store, store_config} -> {store, store_config}
store -> {store, []}
end
end
defp default_store(config) do
backend = Config.get(config, :cache_store_backend, EtsCache)
{ResetTokenCache, [backend: backend]}
end
end