Custom controller for reset password using Pow

I have used Pow for user authentication and successfully created login & logout feature using custom controller (https://hexdocs.pm/pow/custom_controllers.html ) Now I want to create Forgot Password feature. Pow provides reset password extension “PowResetPassword”. I have install Pow extension by reading the documentation (https://hexdocs.pm/pow/pow_reset_password.html)

I have created a new controller for reset password “WEB_PATH/controllers/reset_password/reset_password_controller.ex”

The controller code is shown below.

def new(conn, _params) do
changeset = Pow.Plug.change_user(conn)

render(conn, "new.html", changeset: changeset)

end

def create(conn, %{“user” => user_params}) do

conn
|> PowResetPassword.Plug.create_reset_token(user_params)
|> case do
     {:ok, conn} ->
     conn
     |> put_flash(:info, 'Check your email to reset password')
     |> redirect(to: Routes.reset_password_path(conn, :new))

     {:error, conn} ->
        conn
        |> put_flash(:info, "Error resetting")
        |> redirect(to: Routes.reset_password_path(conn, :new))

   end

end

def update(conn, %{“user” => user_params}) do

PowResetPassword.Plug.update_user_password(conn, user_params)
# {:ok, conn} = Pow.Plug.clear_authenticated_user(conn)

redirect(conn, to: Routes.login_path(conn, :new))

end

My reset password view is:

<%= form_for @changeset, Routes.reset_password_path(@conn, :create), [class: “form-wrap login-form w-100”], fn f -> %>

Reset password

<%= label f, :email, "" %> <%= text_input f, :email, class: "form-control", id: "email", placeholder: "Email" %>
<%= submit "Continue", class: "btn btn-primary w-100" %>

<% end %>

My routes are

signup_path GET / MyAppWeb.RegistrationController :new
signup_path POST / MyAppWeb.RegistrationController :create
login_path GET /login MyAppWeb.SessionController :new
login_path POST /login MyAppWeb.SessionController :create
reset_password_path GET /reset-password/new MyAppWeb.ResetPasswordController :new
reset_password_path POST /reset-password MyAppWeb.ResetPasswordController :create
reset_password_path PATCH /reset-password/:id MyAppWeb.ResetPasswordController :update
PUT /reset-password/:id MyAppWeb.ResetPasswordController :update
reset_password_path GET /reset-password/:id MyAppWeb.ResetPasswordController :edit
logout_path DELETE /logout MyAppWeb.SessionController :delete
game_path GET /game MyAppWeb.PageController :index
websocket WS /socket/websocket MyAppWeb.UserSocket

When I hit submit ‘continue’ button in reset password page with or without entring any email I am getting error as shown below:

CaseClauseError at POST /reset-password
no case clause matching: {:error, #Ecto.Changeset<action: :update, changes: %{}, errors: [password_hash: {“can’t be blank”, [validation: :required]}, password: {“can’t be blank”, [validation: :required]}], data: #MyApp.Users.User<>, valid?: false>, %Plug

I have taken reference from the Pow module PowResetPassword.Phoenix.ResetPasswordController present in my project “deps” folder but not sure which one to use in “create” , “edit” and “update”. please suggest how can I over come this issue.

Your case statement is invalid. PowResetPassword.Plug.create_reset_token/2 returns {:ok, %{token: token, user: user}, conn} or {:error, changeset, conn}.

For security you should also respond with success no matter the result:

def create(conn, %{“user” => user_params}) do
  conn
  |> PowResetPassword.Plug.create_reset_token(user_params)
  |> case do
    {:ok, %{token: token, user: user}, conn} ->
      # Send e-mail

      conn
      |> put_flash(:info, 'Check your email to reset password')
      |> redirect(to: Routes.reset_password_path(conn, :new))

    {:error, conn} ->
      conn
      |> put_flash(:info, 'Check your email to reset password')
      |> redirect(to: Routes.reset_password_path(conn, :new))
  end
end

Take a look at the controller for more.

2 Likes

Thanks for the solution it works now I am not getting any error but still there is an issue I was assuming that there should be some entry in “user” table for the column “email_confirmation_token”, “email_confirmed_at” or in “unconfirmed_email”.

When run forgot password it works well showing me flash message “Check your email to reset password” and when I looked into the database for the corresponding user there is no update in the database column.

I had followed the documentation https://hexdocs.pm/pow/README.html to “Add extensions support” and install the PowResetPassword and PowEmailConfirmation extensions

Please help how to insert token data into my database?

Those columns will only be filled during registration create/update as they are part of the PowEmailConfirmation extension.

PowResetPassword stores the reset token in the cache store.

@danschultzer Thanks for your reply, but I have one more question

> def create(conn, %{“user” => user_params}) do
>   conn
>   |> PowResetPassword.Plug.create_reset_token(user_params)
>   |> case do
>     {:ok, %{token: token, user: user}, conn} ->
>       # How to send email here?
> 
>       conn
>       |> put_flash(:info, 'Check your email to reset password')
>       |> redirect(to: Routes.reset_password_path(conn, :new))
> 
>     {:error, conn} ->
>       conn
>       |> put_flash(:info, 'Check your email to reset password')
>       |> redirect(to: Routes.reset_password_path(conn, :new))
>   end
> end

How can I send email after reset token in the code above?

Just for anyone who’s reading, the question was answered on Github: https://github.com/danschultzer/pow/issues/354#issuecomment-565733371

2 Likes

I am using Pow and PowAssent for API-based app and always getting the result like:

{:error, changeset, _conn} = PowResetPassword.Plug.create_reset_token(conn, %{"email" => "user@example.com"})

changeset # => #Ecto.Changeset<action: nil,changes: %{},errors: [password: {"can't be blank", [validation: :required]}],data: #Handshake.Accounts.User<>,valid?: false

why it requires a password?

It’s because the same changeset is used as when the password gets changed. That’s a bug and it should just return a blank changeset. I’ve opened an issue on Github and will fix it promptly: https://github.com/danschultzer/pow/issues/482

@danschultzer I see. Thank you a lot for your amazing work.

1 Like

Hi there! This is great. Thanks for this forum. I have everything working, except it’s giving me a false positive when I call “update_user_password”. It appears it never actually hashes the password in that function for me. I could hash the password and then pass it to that method, but for me to hash it I need to know the “secret”. “Pow.Ecto.Schema.Password.pbkdf2_hash()” Would the secret be something like the “SECRET_KEY_BASE” environment variable? Or how exactly does calling “PowResetPassword.Plug.update_user_password” supposed to work?

Basically I call “PowResetPassword.Plug.load_user_by_token” before and then pass the conn to “PowResetPassword.Plug.update_user_password”. Everything is great other than it not actually updating it in the DB field / hashing it.

Let me know. Thank you friends

Ahhhhhh. I figured it out. Haha. Whoops. I was using my own changeset instead of what I should have been doing. Inside the changeset is where the magic happens.

I now call these methods inside my changeset:

      |> Pow.Ecto.Schema.Changeset.confirm_password_changeset(attrs, @pow_config)
      |> Pow.Ecto.Schema.Changeset.new_password_changeset(attrs, @pow_config)

Thanks!