I think this idea is pretty useful if you're dealing with customers, who instead of waiting for the email will re-request 3 or 4 times, at which point the first one might turn up, but will by now have an invalid link. Hysteresis or just re-sending the same link are nice to have, but as I mentioned above it's no longer (?) in the devise code, which just handles expiring old reset requests, not limiting the sending of new ones.
I've gone with a simplified version of trh's idea, which selectively forwards to the original devise code. In case there's been a request sent within the last hour it just pretends it's sent it again, and assumes that Mailgun or whoever you are using will get the message where it needs to go.
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
Behaves like this:
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
Bonus points for updating it to re-send the original email with the same link, as in this test:
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