diff --git a/spec/factories/authentication_token.rb b/spec/factories/authentication_token.rb new file mode 100644 index 00000000..f3102284 --- /dev/null +++ b/spec/factories/authentication_token.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :authentication_token, class: 'AuthenticationToken' do + created_by_external_user + shared_secret { SecureRandom.hex(32) } + expire_at { 7.days.from_now } + + trait :invalid do + expire_at { 8.days.ago } + end + end +end diff --git a/spec/features/authentication_spec.rb b/spec/features/authentication_spec.rb index a5cf7a59..8e3fa21c 100644 --- a/spec/features/authentication_spec.rb +++ b/spec/features/authentication_spec.rb @@ -32,6 +32,52 @@ describe 'Authentication' do expect(page).to have_content(I18n.t('sessions.create.failure')) end end + + context 'with no authentication token' do + let(:request_for_comment) { create(:rfc_with_comment, user: user) } + + it 'denies access to the request for comment' do + mail.deliver_now + visit(rfc_link) + expect(page).not_to have_content(request_for_comment.exercise.title) + expect(response).to redirect_to(root_path) + expect(page).to have_content(I18n.t('application.not_authorized')) + end + end + + context 'with an authentication token' do + let(:user) { create(:learner) } + let(:request_for_comment) { create(:rfc_with_comment, user: user) } + let(:commenting_user) { InternalUser.create(attributes_for(:teacher)) } + let(:mail) { UserMailer.got_new_comment(request_for_comment.comments.first, request_for_comment, commenting_user) } + let(:rfc_link) { request_for_comment_url(request_for_comment, token: token.shared_secret) } + + before { allow(AuthenticationToken).to receive(:generate!).with(user).and_return(token).once } + + context 'when the token is valid' do + let(:token) { create(:authentication_token, user: user) } + + it 'allows access to the request for comment' do + mail.deliver_now + visit(rfc_link) + expect(current_url).to be(rfc_link) + expect(response).to have_http_status :ok + expect(page).to have_content(request_for_comment.exercise.title) + end + end + + context 'with an expired authentication token' do + let(:token) { create(:authentication_token, :invalid, user: user) } + + it 'denies access to the request for comment' do + mail.deliver_now + visit(rfc_link) + expect(page).not_to have_content(request_for_comment.exercise.title) + expect(response).to redirect_to(root_path) + expect(page).to have_content(I18n.t('application.not_authorized')) + end + end + end end context 'when signed in' do diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 762ad8f7..1bd68eb3 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -14,7 +14,7 @@ describe UserMailer do end it 'sets the correct sender' do - expect(mail.from).to include(CodeOcean::Application.config.action_mailer[:default_options][:from]) + expect(mail.from).to include('codeocean@hpi.de') end it 'sets the correct subject' do @@ -45,7 +45,7 @@ describe UserMailer do end it 'sets the correct sender' do - expect(mail.from).to include(CodeOcean::Application.config.action_mailer[:default_options][:from]) + expect(mail.from).to include('codeocean@hpi.de') end it 'sets the correct subject' do @@ -69,7 +69,7 @@ describe UserMailer do let(:mail) { described_class.got_new_comment(request_for_comment.comments.first, request_for_comment, commenting_user).deliver_now } it 'sets the correct sender' do - expect(mail.from).to include(CodeOcean::Application.config.action_mailer[:default_options][:from]) + expect(mail.from).to include('codeocean@hpi.de') end it 'sets the correct subject' do @@ -81,22 +81,17 @@ describe UserMailer do end it 'includes the correct URL' do - expect(mail.body).to include(request_for_comment_url(request_for_comment)) + expect(mail.body).to include(request_for_comment_url(request_for_comment, token: token.shared_secret)) end - it 'includes the correct authentication token' do - expect(mail.body).to include(token.shared_secret) - end - - it 'sets a new authentication token' do + it 'creates a new authentication token' do expect { mail }.to change(AuthenticationToken, :count).by(1) end - it 'sets a non expired authentication token' do + it 'sets a non-expired authentication token' do mail - expect(token.expire_at.future?).to be(true) - token.expire_at -= 8.days - expect(token.expire_at.future?).to be(false) + # A five minute tolerance is allowed to account for the time difference between `now` and the creation timestamp of the token. + expect(token.expire_at - Time.zone.now).to be_within(5.minutes).of(7.days) end end @@ -109,7 +104,7 @@ describe UserMailer do let(:mail) { described_class.got_new_comment_for_subscription(request_for_comment.comments.first, subscription, from_user).deliver_now } it 'sets the correct sender' do - expect(mail.from).to include(CodeOcean::Application.config.action_mailer[:default_options][:from]) + expect(mail.from).to include('codeocean@hpi.de') end it 'sets the correct subject' do @@ -121,22 +116,17 @@ describe UserMailer do end it 'includes the correct URL' do - expect(mail.body).to include(request_for_comment_url(subscription.request_for_comment)) + expect(mail.body).to include(request_for_comment_url(subscription.request_for_comment, token: token.shared_secret)) end - it 'includes the correct authentication token' do - expect(mail.body).to include(token.shared_secret) - end - - it 'sets a new authentication token' do + it 'creates a new authentication token' do expect { mail }.to change(AuthenticationToken, :count).by(1) end - it 'sets a non expired authentication token' do + it 'sets a non-expired authentication token' do mail - expect(token.expire_at.future?).to be(true) - token.expire_at -= 8.days - expect(token.expire_at.future?).to be(false) + # A five minute tolerance is allowed to account for the time difference between `now` and the creation timestamp of the token. + expect(token.expire_at - Time.zone.now).to be_within(5.minutes).of(7.days) end end @@ -148,7 +138,7 @@ describe UserMailer do let(:mail) { described_class.send_thank_you_note(request_for_comments, receiver).deliver_now } it 'sets the correct sender' do - expect(mail.from).to include(CodeOcean::Application.config.action_mailer[:default_options][:from]) + expect(mail.from).to include('codeocean@hpi.de') end it 'sets the correct subject' do @@ -160,22 +150,17 @@ describe UserMailer do end it 'includes the correct URL' do - expect(mail.body).to include(request_for_comment_url(request_for_comments)) + expect(mail.body).to include(request_for_comment_url(request_for_comments, token: token.shared_secret)) end - it 'includes the correct authentication token' do - expect(mail.body).to include(token.shared_secret) - end - - it 'sets a new authentication token' do + it 'creates a new authentication token' do expect { mail }.to change(AuthenticationToken, :count).by(1) end - it 'sets a non expired authentication token' do + it 'sets a non-expired authentication token' do mail - expect(token.expire_at.future?).to be(true) - token.expire_at -= 8.days - expect(token.expire_at.future?).to be(false) + # A five minute tolerance is allowed to account for the time difference between `now` and the creation timestamp of the token. + expect(token.expire_at - Time.zone.now).to be_within(5.minutes).of(7.days) end end end