diff --git a/app/password_reset.rb b/app/password_reset.rb index 7312a839..1e98ab23 100644 --- a/app/password_reset.rb +++ b/app/password_reset.rb @@ -13,21 +13,23 @@ post '/send_password_reset' do sites = Site.get_recovery_sites_with_email params[:email] if sites.length > 0 - token = SecureRandom.uuid.gsub('-', '') + token = SecureRandom.uuid.gsub('-', '')+'-'+Time.now.to_i.to_s sites.each do |site| next unless site.parent? site.password_reset_token = token site.save_changes validate: false body = <<-EOT -Hello! This is the Neocities cat, and I have received a password reset request for your e-mail address. +Hello! This is the Penelope the Neocities cat, and I have received a password reset request for your e-mail address. -Go to this URL to reset your password: https://neocities.org/password_reset_confirm?username=#{Rack::Utils.escape(site.username)}&token=#{token} +Go to this URL to reset your password: https://neocities.org/password_reset_confirm?username=#{Rack::Utils.escape(site.username)}&token=#{Rack::Utils.escape(token)} + +This link will expire in 24 hours. If you didn't request this password reset, you can ignore it. Or hide under a bed. Or take a nap. Your call. Meow, -the Neocities Cat +Penelope EOT body.strip! @@ -61,7 +63,13 @@ get '/password_reset_confirm' do redirect '/' end - reset_site.password_reset_token = nil + timestamp = Time.at(reset_site.password_reset_token.split('-').last.to_i) + + if Time.now.to_i - timestamp.to_i > Site::PASSWORD_RESET_EXPIRATION_TIME + flash[:error] = 'Token has expired.' + redirect '/' + end + reset_site.password_reset_confirmed = true reset_site.save_changes diff --git a/models/site.rb b/models/site.rb index f747a522..cce87038 100644 --- a/models/site.rb +++ b/models/site.rb @@ -178,6 +178,8 @@ class Site < Sequel::Model PHONE_VERIFICATION_EXPIRATION_TIME = 10.minutes PHONE_VERIFICATION_LOCKOUT_ATTEMPTS = 3 + PASSWORD_RESET_EXPIRATION_TIME = 24.hours + many_to_many :tags one_to_many :profile_comments diff --git a/tests/acceptance/password_reset_tests.rb b/tests/acceptance/password_reset_tests.rb index b893ce37..1a8391a8 100644 --- a/tests/acceptance/password_reset_tests.rb +++ b/tests/acceptance/password_reset_tests.rb @@ -74,6 +74,9 @@ describe '/password_reset' do visit "/password_reset_confirm?#{Rack::Utils.build_query username: @site.username, token: @site.reload.password_reset_token}" + _(@site.reload.password_reset_token).wont_be_nil + _(@site.password_reset_confirmed).must_equal true + _(page.current_url).must_match /.+\/settings#password/ fill_in 'new_password', with: 'n3wp4s$' @@ -88,4 +91,19 @@ describe '/password_reset' do _(@site.password_reset_confirmed).must_equal false end + it 'fails if timestamp is too old' do + @site = Fabricate :site + visit '/password_reset' + fill_in 'email', with: @site.email + click_button 'Send Reset Token' + + @site.update password_reset_token: "#{SecureRandom.hex}-#{(Time.now - Site::PASSWORD_RESET_EXPIRATION_TIME - 1).to_i}" + + visit "/password_reset_confirm?#{Rack::Utils.build_query username: @site.username, token: @site.reload.password_reset_token}" + + _(page).must_have_content 'Token has expired' + _(@site.reload.password_reset_token).wont_be_nil + _(@site.password_reset_confirmed).must_equal false + end + end