Upgrading to devise 3.1 => getting Reset password token is invalid
Asked Answered
N

6

40

Solution

Thanks to this gist form Steven Harman, I got it working. devise_mail_helpers.rb

module Features
  module MailHelpers

    def last_email
      ActionMailer::Base.deliveries[0]
    end

    # Can be used like:
    #  extract_token_from_email(:reset_password)
    def extract_token_from_email(token_name)
      mail_body = last_email.body.to_s
      mail_body[/#{token_name.to_s}_token=([^"]+)/, 1]
    end

  end
end

I added the file devise_mail_helpers.rb to the same folder as the features specs and wrote this spec.

require 'devise_mail_helpers.rb'
include Features
include MailHelpers
describe "PasswordResets" do
  it "emails user when requesting password reset" do
    user = FactoryGirl.create(:user)
    visit root_url
    find("#login_link").click
    click_link "Forgot your password?"
    fill_in "Email", :with => user.email
    click_button "Send instructions"
    current_path.should eq('/users/sign_in')
    page.should have_content("You will receive an email with instructions about how to reset your password in a few minutes.")
    last_email.to.should include(user.email)
    token = extract_token_from_email(:reset_password) # Here I call the MailHelper form above
    visit edit_password_url(reset_password_token: token)
    fill_in "user_password", :with => "foobar"
    fill_in "user_password_confirmation", :with => "foobar1"
    find('.signup_firm').find(".submit").click
    page.should have_content("Password confirmation doesn't match Password")
  end
 end

This takes care of the specs, to make it work in the browser look at Dave's answer below.

Original Question

In my rails 4 app, I've upgraded devise to 3.1 and ran rails s, then I got this:

`raise_no_secret_key': Devise.secret_key was not set. 
 Please add the following to your Devise initializer: (RuntimeError)
 config.secret_key = '--secret--'

I added the secret key to the devise initializer.

After this I get the following error when I try to reset the password

Reset password token is invalid

It seems like the token that gets sent in the email is not correct. Everything else is working. I logging in and out like a warm knife trough butter.

Update

Now I guess that it's got to be something with the encryption of the reset_password_token Here from the feature spec:

user = FactoryGirl.create(:user, 
 :reset_password_token => "something", 
 :reset_password_sent_at => 1.hour.ago)
visit edit_password_url(user, :reset_password_token => 
  user.reset_password_token)
fill_in "user_password", :with => "foobar"
click_button "Change my password"
page.should have_content("Password confirmation doesn't match Password")

the error occured is:

Failure/Error: page.should have_content
("Password confirmation doesn't match Password")        
expected to find text "Password confirmation doesn't match Password" in 
"Reset password token is invalid"

Any ideas on what I am missing?

Nipissing answered 6/9, 2013 at 15:47 Comment(0)
E
91

You commented on my similar question a bit ago, and I found an answer that might help you as well.

Upgrading to Devise 3.1.0 left some 'cruft' in a view that I hadn't touched in a while. According to this blog post, you need to change your Devise mailer to use @token instead of the old @resource.confirmation_token.

Find this in app/views/<user>/mailer/reset_password_instructions.html.erb and change it to something like:

<p>Hello <%= @resource.email %>!</p>
<p>Someone has requested a link to change your password, and you can do this through the link below.</p>
<p><%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @token) %></p>
<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>

This should fix any token-based confirmation problems you're having. This is likely to fix any unlock or confirmation token problems as well.

Emergence answered 7/9, 2013 at 16:1 Comment(4)
This is the correct solution to make it work om the browser, but how to test it?Nipissing
At a glance, your Rspec test above looks like it should do the trick. Assuming the 'invalid token' problem is gone, and if it's failing at a specific spot, you should share the error. Strategically speaking, combining Rspec or Cucumber with email_spec should allow you to automatically test this. My linked question and this wiki have more information about how to test Devise with Cucumber.Emergence
the test is passing. Before I fixed the problem it was failing with 'invalid token'.Nipissing
If you are using custom mailer you have to provide the token to the views by def reset_password_instructions(user, token, opts={}) @token = token .....Urethritis
M
8

FYI, if you're trying to send a reset password token via another means (i.e. different mailer), you can use code like this (dug out of Devise source), in your User class:

def send_invitation
  raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)

  self.reset_password_token   = enc
  self.reset_password_sent_at = Time.now.utc
  self.save(:validate => false)

  Notifier.signup_notification(contactable: self, token: raw).deliver
end
Mor answered 6/1, 2014 at 20:48 Comment(1)
The key is self.reset_password_token = enc, and setting @token = raw. Thank you so much~!Ced
D
7

I guess you've upgraded Devise to v3.1 not v3.01, because of config.secret_key. So I think it is somehow related to new devise feature - secret key.
I found two commits for secret key feature that can be helpful to better understanding: https://github.com/plataformatec/devise/commit/32648027e282eb4c0f4f42e9c9cc0c961765faa8 https://github.com/plataformatec/devise/commit/d56641f514f54da04f778b2a9b816561df7910c2

Probably you will find something useful for you on http://blog.plataformatec.com.br/2013/08/devise-3-1-now-with-more-secure-defaults/ as well.
Also you can grep reset_password_token on https://github.com/plataformatec/devise/compare/v3.0...v3.1.0.

EDIT
Read on http://blog.plataformatec.com.br/2013/08/devise-3-1-now-with-more-secure-defaults/:

  • The Devise mailer now receives one extra token argument on each method. If you have customized the Devise mailer, you will have to update it. All mailers views also need to be updated to use @token, as shown here, instead of getting the token directly from the resource;
Depside answered 7/9, 2013 at 11:10 Comment(1)
Thanks! I've investigated a bit more. I know of the changes to the token handling in Devise. So, I got to work in the browser. But, how to test it?. There are some tips in the comment on the blog.By the way I corrected the 3.01 typo to 3.1.Nipissing
I
7

I had this error on specs. I was trying to manually set reset_password_token on User so I could just pass in the token to the edit_user_password_path. However, the reset tokens are hashed, so setting it manually won't work. Oops! To avoid this error, I set reset_token equal to the actual token generated which is returned by user.send_reset_password_instructions.

Working spec:

require 'spec_helper'

feature 'User resets password' do
  scenario 'fills out reset form' do
    user = create(:user)
    reset_token = user.send_reset_password_instructions
    new_password = 'newpassword!'
    visit edit_user_password_path(user, reset_password_token: reset_token)

    fill_in :user_password, with: new_password
    fill_in :user_password_confirmation, with: new_password
    click_button 'Change my password'

    expect(page).to have_content(
      'Your password was changed successfully. You are now signed in.'
    )
  end
end
Identify answered 23/9, 2013 at 1:45 Comment(0)
G
2

As others have noted: the reason is that the view which generates the mail which includes the reset password link needs to be changed.

I saw this error because I was still using the devise-i18n-views gem, which generates the old link. Removing that gem and relying on the views which are now a part of the devise-i18n gem solved the problem for me.

Glyptography answered 23/8, 2016 at 10:38 Comment(0)
H
1

In your devise reset password template make sure following content should correct:

=link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @token)

Hammerless answered 17/10, 2014 at 9:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.