Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

249 - Fix invalid access token error #261

Merged
merged 16 commits into from
Jan 23, 2024
Merged
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ yarn-debug.log*
# Ignore Vendor Local
/vendor/bundle --local
/vendor/bundle

# Ignore the files specific to VSCode
.vscode/*
1 change: 1 addition & 0 deletions app/controllers/admin_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def login; end

def validate
if check_credentials(params[:username], params[:password])
reset_session
session[:admin] = true
redirect_to publications_path
else
Expand Down
1 change: 1 addition & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

class ApplicationController < ActionController::Base
include Pagy::Backend
include ExceptionHandlingManager
include UserAuthentication

prepend_before_action :check_date
Expand Down
36 changes: 36 additions & 0 deletions app/controllers/concerns/exception_handling_manager.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

# ExceptionHandlingManager is a concern designed to centralize and manage
# the handling of various application-specific exceptions.
#
# Currently, it includes handling for:
# - ActionController::InvalidAuthenticityToken
#
# Future additions may include but are not limited to:
# - RecordNotFound
# - TimeoutErrors
# - CustomApplicationErrors
#
# This concern is included in the ApplicationController, so all controllers inherit
module ExceptionHandlingManager
extend ActiveSupport::Concern

included do
rescue_from ActionController::InvalidAuthenticityToken, with: :handle_invalid_token
end

private

def handle_invalid_token(exception)
Rails.logger.warn("InvalidAuthenticityToken occurred: #{exception}")
user_was_admin = session[:admin]
reset_session
flash.keep[:danger] = 'Your session has expired. Please log in again.'

if user_was_admin
redirect_to manage_path
else
redirect_to root_path
end
end
end
2 changes: 1 addition & 1 deletion app/controllers/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# app/controllers/pages_controller.rb
class PagesController < ApplicationController
skip_before_action :check_date
skip_before_action :check_date, :require_authenticated_user
ALLOWED_PAGES = %w[closed finished].freeze

def show
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/submitters_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def create
if @submitter.save
reset_session
session[:submitter_id] = @submitter.id
# Change to home page

flash.keep[:success] = 'Your account was successfully created.'
format.html { redirect_to publications_path }
format.json { render :show, status: :created, location: @submitter }
Expand Down
69 changes: 69 additions & 0 deletions spec/controllers/concerns/exception_handling_manager_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe ExceptionHandlingManager, type: :controller do
controller(ApplicationController) do
include ExceptionHandlingManager

def index
render plain: 'Hello, world!'
end
end

let(:specific_exception) { ActionController::InvalidAuthenticityToken.new('Test Exception Message') }
let(:submitter) { FactoryBot.create(:submitter) }
let(:submitter_session) { { submitter_id: submitter.id } }
let(:admin_session) { { admin: true } }

before do
routes.draw { get 'index' => 'anonymous#index' }
allow(controller).to receive(:index).and_raise(specific_exception)
end

context 'as a submitter' do
let(:session) { submitter_session }

it 'logs the exception to the Rails logger' do
allow(Rails.logger).to receive(:warn)
get(:index, session:)
expect(Rails.logger).to have_received(:warn).with('InvalidAuthenticityToken occurred: Test Exception Message')
end

it 'resets the session' do
get(:index, session:)
expect(controller.session[:admin]).to be_nil
expect(controller.session[:submitter_id]).to be_nil
end

it 'sets a flash message and redirects to the root path' do
get(:index, session:)
expect(response.status).to eq(302)
expect(response).to redirect_to(root_path)
expect(flash[:danger]).to eq('Your session has expired. Please log in again.')
end
end

context 'as an admin' do
let(:session) { admin_session }

it 'logs the exception to the Rails logger' do
allow(Rails.logger).to receive(:warn)
get(:index, session:)
expect(Rails.logger).to have_received(:warn).with('InvalidAuthenticityToken occurred: Test Exception Message')
end

it 'resets the session' do
get(:index, session:)
expect(controller.session[:admin]).to be_nil
expect(controller.session[:submitter_id]).to be_nil
end

it 'sets a flash message and redirects to the manage path' do
get(:index, session:)
expect(response.status).to eq(302)
expect(response).to redirect_to(manage_path)
expect(flash[:danger]).to eq('Your session has expired. Please log in again.')
end
end
end
6 changes: 4 additions & 2 deletions spec/controllers/submitters_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
context 'with valid params' do
it 'clears the old session' do
post :create, params: { submitter: valid_attributes }, session: old_session
expect(session[:submitter_id]).not_to be_nil
expect(session[:submitter_id]).to eq(Submitter.last.id)
expect(session[:submitter_id]).to_not be(old_submitter.id)
expect(session[:submitter_id]).to_not be_nil
expect(session[:some_old_key]).to be_nil
end

Expand All @@ -63,6 +63,8 @@
it 'redirects to the publications show page' do
post :create, params: { submitter: valid_attributes }, session: {}
expect(response).to redirect_to(publications_path)
expect(flash[:success]).to eql 'Your account was successfully created.'
expect(flash[:error]).to be_nil
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Application Behavior', type: :feature do
let(:submitter) { FactoryBot.build(:submitter) }

before do
ActionController::Base.allow_forgery_protection = true
end

after do
ActionController::Base.allow_forgery_protection = false
end

context 'when an invalid authenticity token is provided along with a missing or invalid session ID' do
it 'redirects to the root page with an error message' do
# The user never tries to log in, otherwise they would have a session.
# The user never gets to fill in information, so there is no authenticity token to make invalid.
visit publications_path
expect_to_be_on_root_page_with_login_message
end

it 'allows the user to log in after having been redirected to the root page with an error message' do
visit publications_path
visit_publications_page_as_submitter(submitter)
expect(page).to have_current_path(publications_path)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Handling of invalid authenticity token', type: :feature, js: true do
let(:submitter) { FactoryBot.create(:submitter) }

before do
ActionController::Base.allow_forgery_protection = true
end

after do
ActionController::Base.allow_forgery_protection = false
end

context 'when trying to log in' do
context 'as a submitter' do
it 'triggers an inauthentic token error and redirects to the root path' do
visit root_path
fill_in('submitter[first_name]', with: submitter.first_name)
fill_in('submitter[last_name]', with: submitter.last_name)
find_by_id('submitter_college').find(:xpath, "option[#{submitter.college}]").select_option
fill_in('submitter[department]', with: submitter.department)
fill_in('submitter[mailing_address]', with: submitter.mailing_address)
fill_in('submitter[phone_number]', with: submitter.phone_number)
fill_in('submitter[email_address]', with: submitter.email_address)
make_authenticity_token_invalid
click_on('Next')
expect_to_be_on_root_page_with_expired_error
end
end

context 'as an admin' do
before do
allow(ENV).to receive(:fetch).and_call_original
end
context 'when the user was previously in an admin session' do
# We are assuming that the previous session was an admin session.
# If it were not, then the user would be redirected to the root path.
it 'triggers an inauthentic token error and redirects to the manage path' do
visit_publications_page_as_admin
visit manage_path
fill_in('username', with: ENV.fetch('ADMIN_USERNAME', nil))
fill_in('password', with: ENV.fetch('ADMIN_PASSWORD', nil))
make_authenticity_token_invalid
click_on('Submit')
expect_to_be_on_manage_page_with_expired_error
end
end

context 'when the user was not previously in an admin session' do
it 'triggers an inauthentic token error and redirects to the root path' do
# We are assuming that the session[:admin] is not already set.
# If it were, then the user would be redirected to the manage path.
visit manage_path
fill_in('username', with: ENV.fetch('ADMIN_USERNAME', nil))
fill_in('password', with: ENV.fetch('ADMIN_PASSWORD', nil))
make_authenticity_token_invalid
click_on('Submit')
expect_to_be_on_root_page_with_expired_error
end
end
end
end

context 'when trying to add a publication' do
context 'as a submitter' do
it 'triggers an inauthentic token error and redirects to the root path' do
visit_publications_page_as_submitter(submitter)
first('a', text: 'New').click # There is no authenticity check for this button
expect(page).to have_current_path(new_artwork_path)
make_authenticity_token_invalid # New artwork will submit an authenticity token.

fill_in('artwork[author_first_name][]', with: 'Test')
fill_in('artwork[author_last_name][]', with: 'Artist')
fill_in('artwork[work_title]', with: 'Test Artwork')
click_on('Submit')
expect_to_be_on_root_page_with_expired_error
end
end

context 'as an admin' do
before do
FactoryBot.create(:artwork) # create an artwork to edit
end
it 'triggers an inauthentic token error and redirects to the manage path' do
visit_publications_page_as_admin
expect(page).to have_current_path(publications_path)
find('i.fas.fa-edit', match: :first).click
expect(page).to have_current_path(edit_artwork_path(Artwork.first))
make_authenticity_token_invalid
click_on('Submit')
expect_to_be_on_manage_page_with_expired_error
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Application Behavior', type: :feature do
context 'when a user is not logged in' do
# No submitter_id or admin=true
it 'redirects to a login page with a message to submit your information' do
visit publications_path
expect(current_path).to eq(root_path)
expect(page).to have_content('You must submit your information first.')
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Application Behavior', type: :feature, js: true do
before do
ActionController::Base.allow_forgery_protection = true
allow(ENV).to receive(:fetch).and_call_original
allow(ENV).to receive(:fetch).with('EXPIRATION_DATE').and_return('Jan 01 2000')
end

after do
ActionController::Base.allow_forgery_protection = false
end

context 'when accessing outside open dates and with an invalid authenticity token' do
context 'as an admin' do
before do
FactoryBot.create(:artwork) # create an artwork to edit
end
it 'triggers an inauthentic token error and redirects to the root path' do
visit_publications_page_as_admin
expect(page).to have_current_path(publications_path)
find('i.fas.fa-edit', match: :first).click
expect(page).to have_current_path(edit_artwork_path(Artwork.first))
make_authenticity_token_invalid
click_on('Submit')
expect_to_be_on_manage_page_with_expired_error
end
end

context 'as a submitter' do
it 'redirects to the closed page' do
visit root_path
# The user never gets to fill in information, so there is no authenticity token to make invalid.
expect(page).to have_content('The deadline for submissions has passed.')
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Application Behavior', type: :feature do
let(:submitter) { FactoryBot.build(:submitter) }

before do
allow(ENV).to receive(:fetch).with('EXPIRATION_DATE').and_return('Jan 01 2000')
end

context 'when accessing outside open dates and with a missing or invalid session ID' do
it 'redirects to the closed page' do
visit publications_path
expect(page).to have_content('The deadline for submissions has passed.')
end
end
end
Loading