Browse Source

Expired password reset token flow (#233)

* Update devise to return a 422 on token error

* Update view to display formatted error

* Add unit test
pull/237/head
baarkerlounger 3 years ago committed by GitHub
parent
commit
59f7e387fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      Gemfile.lock
  2. 4
      app/controllers/users_controller.rb
  3. 11
      app/presenters/error_summary_full_messages_presenter.rb
  4. 2
      app/views/devise/passwords/edit.html.erb
  5. 11
      spec/presenters/error_summary_full_messages_presenter_spec.rb
  6. 1
      spec/requests/case_log_controller_spec.rb
  7. 1
      spec/requests/soft_validations_controller_spec.rb
  8. 56
      spec/requests/user_controller_spec.rb

24
Gemfile.lock

@ -8,7 +8,7 @@ GIT
GIT GIT
remote: https://github.com/baarkerlounger/devise.git remote: https://github.com/baarkerlounger/devise.git
revision: 0f585ea6683a06858863597628b48610731c2613 revision: 223969286e51b02e78309b7dad908d223d6ba7f5
branch: dluhc-fixes branch: dluhc-fixes
specs: specs:
devise (4.8.1) devise (4.8.1)
@ -322,33 +322,33 @@ GEM
rspec-mocks (~> 3.10) rspec-mocks (~> 3.10)
rspec-support (~> 3.10) rspec-support (~> 3.10)
rspec-support (3.10.3) rspec-support (3.10.3)
rubocop (1.23.0) rubocop (1.25.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.0.0.0) parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0) regexp_parser (>= 1.8, < 3.0)
rexml rexml
rubocop-ast (>= 1.12.0, < 2.0) rubocop-ast (>= 1.15.1, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0) unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.13.0) rubocop-ast (1.15.1)
parser (>= 3.0.1.1) parser (>= 3.0.1.1)
rubocop-govuk (4.2.0) rubocop-govuk (4.3.0)
rubocop (= 1.23.0) rubocop (= 1.25.0)
rubocop-ast (= 1.13.0) rubocop-ast (= 1.15.1)
rubocop-rails (= 2.12.4) rubocop-rails (= 2.13.2)
rubocop-rake (= 0.6.0) rubocop-rake (= 0.6.0)
rubocop-rspec (= 2.6.0) rubocop-rspec (= 2.7.0)
rubocop-performance (1.13.2) rubocop-performance (1.13.2)
rubocop (>= 1.7.0, < 2.0) rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0) rubocop-ast (>= 0.4.0)
rubocop-rails (2.12.4) rubocop-rails (2.13.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0) rubocop (>= 1.7.0, < 2.0)
rubocop-rake (0.6.0) rubocop-rake (0.6.0)
rubocop (~> 1.0) rubocop (~> 1.0)
rubocop-rspec (2.6.0) rubocop-rspec (2.7.0)
rubocop (~> 1.19) rubocop (~> 1.19)
ruby-progressbar (1.11.0) ruby-progressbar (1.11.0)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)

4
app/controllers/users_controller.rb

@ -12,7 +12,7 @@ class UsersController < ApplicationController
redirect_to user_path(@user) redirect_to user_path(@user)
elsif user_params.key?("password") elsif user_params.key?("password")
format_error_messages format_error_messages
render :edit_password, status: :unprocessable_entity render "devise/passwords/edit", status: :unprocessable_entity
else else
format_error_messages format_error_messages
render :edit, status: :unprocessable_entity render :edit, status: :unprocessable_entity
@ -40,7 +40,7 @@ class UsersController < ApplicationController
end end
def edit_password def edit_password
render :edit_password render "devise/passwords/edit"
end end
private private

11
app/presenters/error_summary_full_messages_presenter.rb

@ -0,0 +1,11 @@
class ErrorSummaryFullMessagesPresenter
def initialize(error_messages)
@error_messages = error_messages
end
def formatted_error_messages
@error_messages.map do |attribute, messages|
[attribute, [attribute.to_s.humanize, messages.first].join(" ")]
end
end
end

2
app/views/users/edit_password.html.erb → app/views/devise/passwords/edit.html.erb

@ -10,7 +10,7 @@
<%= form_for(@user, as: :user, html: { method: :patch }) do |f| %> <%= form_for(@user, as: :user, html: { method: :patch }) do |f| %>
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds"> <div class="govuk-grid-column-two-thirds">
<%= f.govuk_error_summary %> <%= f.govuk_error_summary(presenter: ErrorSummaryFullMessagesPresenter) %>
<h1 class="govuk-heading-l"> <h1 class="govuk-heading-l">
<%= content_for(:title) %> <%= content_for(:title) %>

11
spec/presenters/error_summary_full_messages_presenter_spec.rb

@ -0,0 +1,11 @@
require "rails_helper"
RSpec.describe ErrorSummaryFullMessagesPresenter do
let(:error_messages) { { reset_password_token: %w[expired] } }
let(:formatted_error_messages) { [[:reset_password_token, "Reset password token expired"]] }
subject { described_class.new(error_messages) }
it "formats messages to include the attribute name" do
expect(subject.formatted_error_messages).to eq(formatted_error_messages)
end
end

1
spec/requests/case_log_controller_spec.rb

@ -1,5 +1,4 @@
require "rails_helper" require "rails_helper"
require_relative "../request_helper"
RSpec.describe CaseLogsController, type: :request do RSpec.describe CaseLogsController, type: :request do
let(:owning_organisation) { FactoryBot.create(:organisation) } let(:owning_organisation) { FactoryBot.create(:organisation) }

1
spec/requests/soft_validations_controller_spec.rb

@ -1,5 +1,4 @@
require "rails_helper" require "rails_helper"
require_relative "../request_helper"
RSpec.describe SoftValidationsController, type: :request do RSpec.describe SoftValidationsController, type: :request do
let(:params) { { case_log_id: case_log.id } } let(:params) { { case_log_id: case_log.id } }

56
spec/requests/user_controller_spec.rb

@ -1,7 +1,6 @@
require "rails_helper" require "rails_helper"
require_relative "../support/devise"
RSpec.describe UsersController, type: :request do RSpec.describe "password_reset", type: :request do
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
let(:unauthorised_user) { FactoryBot.create(:user) } let(:unauthorised_user) { FactoryBot.create(:user) }
let(:headers) { { "Accept" => "text/html" } } let(:headers) { { "Accept" => "text/html" } }
@ -46,21 +45,48 @@ RSpec.describe UsersController, type: :request do
end end
context "update password" do context "update password" do
let(:params) do context "valid reset token" do
{ let(:params) do
id: user.id, user: { password: new_value, password_confirmation: "something_else" } {
} id: user.id, user: { password: new_value, password_confirmation: "something_else" }
}
end
before do
sign_in user
put "/users/#{user.id}", headers: headers, params: params
end
it "shows an error if passwords don't match" do
expect(response).to have_http_status(:unprocessable_entity)
expect(page).to have_selector("#error-summary-title")
expect(page).to have_content("Password confirmation doesn't match Password")
end
end end
before do context "reset token more than 3 hours old" do
sign_in user let(:raw) { user.send_reset_password_instructions }
put "/users/#{user.id}", headers: headers, params: params let(:params) do
end {
id: user.id,
it "shows an error if passwords don't match" do user: {
expect(response).to have_http_status(:unprocessable_entity) password: new_value,
expect(page).to have_selector("#error-summary-title") password_confirmation: new_value,
expect(page).to have_content("Password confirmation doesn't match Password") reset_password_token: raw,
},
}
end
before do
allow_any_instance_of(User).to receive(:reset_password_sent_at).and_return(4.hours.ago)
put "/users/password", headers: headers, params: params
end
it "shows an error" do
expect(response).to have_http_status(:unprocessable_entity)
expect(page).to have_selector("#error-summary-title")
expect(page).to have_content("Reset password token has expired, please request a new one")
end
end end
end end
end end

Loading…
Cancel
Save