升级到Devise 3.1 => 获取的重置密码令牌无效

40

解决方案

感谢Steven Harman提供的代码片段,我已经使其正常工作。 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

我将文件devise_mail_helpers.rb添加到与功能规格相同的文件夹中,并编写了此规格。

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

这个问题可以通过查看下面Dave的回答来解决。如果要在浏览器中运行,请参考相关规范。

原始问题

在我的Rails 4应用程序中,我已经将devise升级到3.1并运行了rails s,然后我得到了这个错误:

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

我在devise的初始化程序中添加了密钥。
之后,当我尝试重置密码时,出现以下错误:
Reset password token is invalid

看起来电子邮件中发送的令牌不正确。其他一切正常。我像刀子一样轻松地登录和退出。

更新

现在我猜测问题可能与reset_password_token的加密有关。从功能规范中可以看到:

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")

发生的错误是:
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"

有任何想法我漏掉了什么吗?

6个回答

91

您在不久前评论了我的类似问题,我找到了一个答案,可能也会对您有所帮助。

升级到Devise 3.1.0后,在我已经有一段时间没有触碰的视图中留下了一些“垃圾”。根据这篇博客文章,您需要更改您的Devise邮件程序,使用@token而不是旧的@resource.confirmation_token

app/views/<user>/mailer/reset_password_instructions.html.erb中找到它,并将其更改为以下内容:

<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>

这应该可以解决您遇到的任何基于令牌的确认问题。这很可能也会解决任何解锁或确认令牌问题。


这是在浏览器中使其正常工作的正确解决方案,但如何测试它? - Andreas Lyngstad
乍一看,您上面的Rspec测试看起来应该可以解决问题。假设“无效令牌”问题已经消失,如果它在特定位置失败,您应该分享错误信息。从战略上讲,将Rspec或Cucumber与email_spec相结合,应该可以自动测试这个问题。我的相关问题和此wiki页面提供了有关如何使用Cucumber测试Devise的更多信息。 - David Elner
测试通过了。在我修复问题之前,它一直报错为“无效的标记”。 - Andreas Lyngstad
如果您正在使用自定义邮件程序,则必须通过以下方式向视图提供令牌:def reset_password_instructions(user, token, opts={}) @token = token ..... - Ivailo Bardarov

8

如果你想通过其他方式(比如不同的邮件客户端)发送重置密码令牌,可以在你的用户类中使用以下代码(从Devise源代码中挖掘出来的):

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

关键是 self.reset_password_token = enc,并设置 @token = raw。非常感谢! - Mirror318

7

我在测试中遇到了这个错误。我试图手动设置用户的reset_password_token,以便只需将令牌传递到edit_user_password_path中即可。然而,重置令牌是散列的,因此无法手动设置。糟糕!为了避免该错误,我将reset_token设置为由user.send_reset_password_instructions返回的实际令牌。

工作规范:

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

7
我猜你已经将Devise升级到v3.1而不是v3.01,因为config.secret_key。所以我认为它与新的devise功能——密钥有关。
我找到了两个与密钥功能相关的提交,这对于更好地理解可能会有帮助:https://github.com/plataformatec/devise/commit/32648027e282eb4c0f4f42e9c9cc0c961765faa8, https://github.com/plataformatec/devise/commit/d56641f514f54da04f778b2a9b816561df7910c2

你可能也会在http://blog.plataformatec.com.br/2013/08/devise-3-1-now-with-more-secure-defaults/上找到一些有用的东西。
此外,你可以在https://github.com/plataformatec/devise/compare/v3.0...v3.1.0上使用reset_password_token

编辑
阅读http://blog.plataformatec.com.br/2013/08/devise-3-1-now-with-more-secure-defaults/

  • Devise邮件现在在每个方法中都会接收一个额外的令牌参数。如果你已经自定义了Devise邮件,你需要更新它。所有邮件视图也需要更新以使用@token,如此处所示,而不是直接从资源中获取令牌;

谢谢!我进行了更深入的调查。我知道了Devise中令牌处理的更改。所以,我在浏览器中开始工作。但是,如何测试呢?博客的评论中有一些提示。顺便说一下,我已经将3.01的笔误更正为3.1。 - Andreas Lyngstad

2

正如其他人所指出的:原因是生成包含重置密码链接的邮件的视图需要更改。

我看到这个错误是因为我仍在使用 devise-i18n-views gem,它生成旧的链接。删除该 gem 并依赖于现在作为 devise-i18n gem 一部分的视图解决了我的问题。


1

在您的设备重置密码模板中,请确保以下内容正确:

=link_to '更改我的密码', edit_password_url(@resource, :reset_password_token => @token)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接