Rails设计密码重置电子邮件允许多次提交

时间:2013-09-11 15:44:49

标签: ruby-on-rails ruby ajax devise password-recovery

我有以下代码,允许用户以AJAX形式请求密码重置:

<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post },:remote =>'true') do |f| %>
 <%= devise_error_messages! %>
 <div><%= f.label :email %><br />    
 <%= f.email_field :email %></div>
 <div><%= f.submit "Send me reset password instructions" %></div>
<% end %>

这允许这样的行为,即如果用户反复点击按钮,或者在服务器提供响应之前反复按“输入”,则会发送相应的#密码重置电子邮件。

以下是在devise / password_controller.rb

def create
 self.resource = resource_class.send_reset_password_instructions(resource_params)   
 if successfully_sent?(resource)
  flash[:notice] = "You will receive an email with instructions about how to reset your password in a few minutes."
  respond_to do |format|
   format.html #responds with default html file
   format.js 
  end    
 else
  respond_to do |format|
   format.html #responds with default html file
   format.js{ render :js => "$(\".deviseErrors\").html(\"<span class='login-error'>Could not send reset instructions to that address.</span>\");" } #this will be the javascript file we respond with
  end
 end
end

有没有办法只回复第一次提交?

由于

4 个答案:

答案 0 :(得分:3)

我建议使用JavaScript来阻止多次提交。

$('form#reset_password').on('submit', function() {
  $(this).find('input[type="submit"]').attr('disabled', 'disabled')
})

这会将提交按钮设置为“已禁用”状态,用户无法再次提交。

  

有关表单已停用属性的参考:http://www.w3schools.com/tags/att_input_disabled.asp *

添加:响应thr的回答

我浏览了Devise源代码,发现在模型级别应该有一个解决方案。要设置每个重置请求之间允许的最大间隔,请在资源模型中添加

class user < ActiveRecord::Base

  def self.reset_password_with
    1.day
    # Determine the interval. Any time objects will do, say 1.hour
  end
end

然后Devise :: Models :: Recoverable将检查此值以决定是否应发送令牌。我没有验证这个,但它应该工作。

答案 1 :(得分:3)

我认为,如果您与客户打交道,这个想法非常有用,而不是等待电子邮件重新请求3到4次,此时第一次可能会出现,但到现在为止有一个无效的链接。滞后或只是重新发送相同的链接是很好的,但正如我上面提到的,它不再是({3}}中的{?},它只处理过期的旧重置请求,而不是限制发送新的。

我已经选择了trh的简化版本,它有选择地转发到原始设计代码。如果在过去一小时内发送了请求,它只是假装它再次发送,并假设Mailgun或您正在使用的任何人都会收到它需要去的消息。

class Members::PasswordsController < Devise::PasswordsController
  def create
    self.resource = resource_class.find_by_email(resource_params[:email])
    if resource && (!resource.reset_password_sent_at.nil? || Time.now > resource.reset_password_sent_at + 1.hour)
      super
    else
      flash[:notice] = I18n.t('devise.passwords.send_instructions')
      respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name))
    end
  end
end

表现得像这样:

  specify "asking twice sends the email once only, until 1 hour later" do
    member = make_activated_member
    ActionMailer::Base.deliveries.clear
    2.times do
      ensure_on member_dashboard_path
      click_on "Forgotten your password?"
      fill_in "Email", :with => member.email
      click_on "Send me password reset instructions"
    end
    # see for mail helpers https://github.com/bmabey/email-spec/blob/master/lib/email_spec/helpers.rb

    expect(mailbox_for(member.email).length).to eq(1)
    expect(page).to have_content(I18n.t('devise.passwords.send_instructions'))    

    Timecop.travel(Time.now + 2.hours) do
      expect {
        ensure_on member_dashboard_path
        click_on "Forgotten your password?"
        fill_in "Email", :with => member.email
        click_on "Send me password reset instructions"
      }.to change{mailbox_for(member.email).length}.by(+1)
    end
  end

奖励点用于更新它以重新发送具有相同链接的原始电子邮件,如此测试:

  specify "asking twice sends the same link both times" do
    member = make_activated_member
    ActionMailer::Base.deliveries.clear
    2.times do
      visit member_dashboard_path
      click_on "Forgotten your password?"
      fill_in "Email", :with => member.email
      click_on "Send me password reset instructions"
    end
    # see for mail helpers https://github.com/bmabey/email-spec/blob/master/lib/email_spec/helpers.rb

    mails = mailbox_for(member.email)
    expect(mails.length).to eq(2)
    first_mail = mails.first
    second_mail = mails.last

    expect(links_in_email(first_mail)).to eq(links_in_email(second_mail))
  end

答案 2 :(得分:1)

如果你真的只是试图让人们不要双击提交,那么通过javascript限制就像Billy-chan在他的回答中建议的那样。

如果要限制将请求发送到给定用途之间的时间量,则可以设置资源并将该功能包装在if语句中,以检查上次发送密码请求时的时间戳。像这样的东西

def create
  self.resource = resource_class.find_by_email(resource_params[:email])
  if resource.reset_password_sent_at.nil?  ||  Time.now > resource.reset_password_sent_at + 5.minutes
    self.resource = resource_class.send_reset_password_instructions(resource_params)
    if successfully_sent?(resource)
      flash[:notice] = "You will receive an email with instructions about how to reset your password in a few minutes."
      respond_to do |format|
        format.html #responds with default html file
        format.js
      end
    else
      respond_to do |format|
        format.html #responds with default html file
        format.js{ render :js => "$(\".deviseErrors\").html(\"<span class='login-error'>Could not send reset instructions to that address.</span>\");" } #this will be the javascript file we respond with
      end
    end
  else
    flash[:error] = "Passwords can only be reset every 5 minutes."
    respond_to do |format|
      format.html #responds with default html file
      format.js
    end
  end
end

答案 3 :(得分:1)

您可以在Devise中执行以下操作:

class User < ActiveRecord::Base
  def send_reset_password_instructions
    super unless reset_password_sent_at.present? && reset_password_sent_at > DateTime.now - 1.day
  end
end

1.day是允许的两次密码重置之间的时间间隔。