Packages
pow
1.0.39
1.0.39
1.0.38
1.0.37
1.0.36
1.0.35
1.0.34
1.0.33
1.0.32
1.0.31
1.0.30
1.0.29
1.0.28
1.0.27
1.0.26
1.0.25
1.0.24
1.0.23
1.0.22
1.0.21
1.0.20
1.0.19
1.0.18
1.0.17
1.0.16
1.0.15
1.0.14
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-rc.4
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.1.0-rc.1
0.1.0-alpha.8
0.1.0-alpha.7
retired
0.1.0-alpha.6
0.1.0-alpha.5
0.1.0-alpha.4
0.1.0-alpha.3
0.1.0-alpha.2
0.1.0-alpha.1
0.1.0-alpha
Robust user authentication solution
Current section
Files
Jump to
Current section
Files
lib/extensions/reset_password/plug.ex
defmodule PowResetPassword.Plug do
@moduledoc """
Plug helper functions.
"""
alias Plug.Conn
alias Pow.{Config, Plug, Store.Backend.EtsCache, UUID}
alias PowResetPassword.Ecto.Context, as: ResetPasswordContext
alias PowResetPassword.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)
user.__struct__.reset_password_changeset(user, params)
end
defp user_struct(conn) do
conn
|> Plug.fetch_config()
|> Config.user!()
|> struct()
end
# TODO: Remove by 1.1.0
@doc false
@deprecated "No longer used"
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.
The returned `:token` is signed for public consumption using
`Pow.Plug.sign_token/4`. Additionally `Pow.UUID.generate/0` is called whether
the user exists or not to prevent timing attacks.
`: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()
params
|> Map.get("email")
|> ResetPasswordContext.get_by_email(config)
|> case do
nil ->
{:error, change_user(conn, params), conn}
user ->
{store, store_config} = store(config)
store.put(store_config, token, user)
signed = Plug.sign_token(conn, signing_salt(), token, config)
{:ok, %{token: signed, user: user}, conn}
end
end
defp signing_salt(), do: Atom.to_string(__MODULE__)
@doc """
Verifies the signed token and fetches user from store.
If a user is found, it'll be assigned to `conn.assigns` for key
`:reset_password_user`.
A `:pow_reset_password_decoded_token` key will be assigned in `conn.private`
with the decoded token. This is used to invalidate the token when calling
`update_user_password/2`.
The token will be decoded and verified with `Pow.Plug.verify_token/4`.
See `create_reset_token/2` for more on `:reset_password_token_store` config
option.
"""
@spec load_user_by_token(Conn.t(), binary()) :: {:ok, Conn.t()} | {:error, Conn.t()}
def load_user_by_token(conn, signed_token) do
config = Plug.fetch_config(conn)
with {:ok, token} <- Plug.verify_token(conn, signing_salt(), signed_token, config),
user when not is_nil(user) <- fetch_user_from_token(token, config) do
conn =
conn
|> Conn.put_private(:pow_reset_password_decoded_token, token)
|> Conn.assign(:reset_password_user, user)
{:ok, conn}
else
_any -> {:error, conn}
end
end
defp fetch_user_from_token(token, config) do
{store, store_config} = store(config)
store_config
|> store.get(token)
|> case do
:not_found -> nil
user -> user
end
end
# TODO: Remove by 1.1.0
@doc false
@deprecated "Use `load_user_by_token/2` instead"
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.
The user should exist in `conn.assigns` for key `:reset_password_user` and
the decoded token in `conn.private` for key
`:pow_reset_password_decoded_token`. `load_user_by_token/2` will ensure this.
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)
conn
|> reset_password_user()
|> ResetPasswordContext.update_password(params, config)
|> case do
{:ok, user} ->
expire_token(conn, config)
{:ok, user, conn}
{:error, changeset} ->
{:error, changeset, conn}
end
end
defp expire_token(%{private: %{pow_reset_password_decoded_token: token}}, config) do
{store, store_config} = store(config)
store.delete(store_config, token)
end
defp expire_token(_conn, _config) do
IO.warn("no `:pow_reset_password_decoded_token` key found in `conn.private`, please call `#{inspect __MODULE__}.load_user_by_token/2` first")
:ok
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) do
{store, store_config} -> {store, store_opts(config, store_config)}
nil -> {ResetTokenCache, store_opts(config)}
end
end
defp store_opts(config, store_config \\ []) do
store_config
|> Keyword.put_new(:backend, Config.get(config, :cache_store_backend, EtsCache))
|> Keyword.put_new(:pow_config, config)
end
end