diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 19d92a6f..7ed8bcc9 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -127,11 +127,14 @@ class ApplicationController < ActionController::Base respond_to do |format| format.any do # Prevent redirect loop - if request.url == request.referer + if request.url == request.referer || request.referer&.match?(sign_in_path) redirect_to :root, alert: message # Redirect to main domain if the request originated from our render_host elsif request.path == '/' && request.host == RENDER_HOST redirect_to Rails.application.config.action_mailer.default_url_options, allow_other_host: true + elsif current_user.nil? && status == :unauthorized + session[:return_to_url] = request.fullpath if current_user.nil? + redirect_to sign_in_path, alert: t('application.not_signed_in') else redirect_back fallback_location: :root, allow_other_host: false, alert: message end diff --git a/app/views/sessions/new.html.slim b/app/views/sessions/new.html.slim index d7ce1e31..46ebf78a 100644 --- a/app/views/sessions/new.html.slim +++ b/app/views/sessions/new.html.slim @@ -1,5 +1,13 @@ h1 = t('.headline') +.lead + .card.border-info-subtle.mb-3 + .card-header + i.fa-solid.fa-circle-info.text-info + strong.text-info + => t('.instructors_only') + = t('.instructors_only_explanation', application_name:) + = form_tag(sessions_path) do .mb-3 = label_tag(:email, t('activerecord.attributes.internal_user.email')) diff --git a/config/locales/de.yml b/config/locales/de.yml index a753b8c1..fe7792cc 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -294,6 +294,7 @@ de: application: not_authorized: Sie sind nicht berechtigt, diese Aktion auszuführen. not_found: Die angeforderte Ressource konnte nicht gefunden werden. + not_signed_in: Sie müssen angemeldet sein, um diese Aktion auszuführen. welcome: text_signed_in_as_external_user: 'Bitte rufen Sie %{application_name} von einer E-Learning-Plattform auf.' text_signed_in_as_internal_user: 'Schön, Sie zu sehen, %{user_name}!' @@ -892,6 +893,8 @@ de: new: forgot_password: Passwort vergessen? headline: Anmelden + instructors_only: 'Zugriff ausschließlich für Lehrende:' + instructors_only_explanation: Das Anmelden mittels Passwort ist ausschließlich Lehrenden vorbehalten. Als Lernende:r greifen Sie bitte über die E-Learning-Plattform auf %{application_name} zu, um direkt zu einer Aufgabe zu gelangen. link: Anmelden remember_me: Angemeldet bleiben oauth: diff --git a/config/locales/en.yml b/config/locales/en.yml index 9ae0335f..0b0654a2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -294,6 +294,7 @@ en: application: not_authorized: You are not authorized to perform this action. not_found: The requested resource could not be found. + not_signed_in: You must be signed in to perform this action. welcome: text_signed_in_as_external_user: 'Please access %{application_name} from an e-learning platform.' text_signed_in_as_internal_user: 'Good to see you, %{user_name}!' @@ -893,6 +894,8 @@ en: forgot_password: Forgot password? headline: Sign In link: Sign In + instructors_only: 'Exclusive access for instructors:' + instructors_only_explanation: Signing in via password is reserved exclusively for instructors. As a learner, please access %{application_name} via the e-learning platform to proceed directly to an exercise. remember_me: Remember me oauth: failure: 'Sorry, something went wrong.' diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index dd7726fa..105e8d38 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -30,11 +30,26 @@ RSpec.describe ApplicationController do describe '#render_not_authorized' do before do allow(controller).to receive(:welcome) { controller.send(:render_not_authorized) } + login_user(user) if defined?(user) get :welcome end - expect_flash_message(:alert, I18n.t('application.not_authorized')) - expect_redirect(:root) + expect_flash_message(:alert, I18n.t('application.not_signed_in')) + expect_redirect(:sign_in) + + context 'with an admin' do + let(:user) { create(:admin) } + + expect_flash_message(:alert, I18n.t('application.not_authorized')) + expect_redirect(:root) + end + + context 'with a teacher' do + let(:user) { create(:teacher) } + + expect_flash_message(:alert, I18n.t('application.not_authorized')) + expect_redirect(:root) + end end describe '#render_not_found' do @@ -44,19 +59,21 @@ RSpec.describe ApplicationController do get :welcome end - expect_flash_message(:alert, I18n.t('application.not_authorized')) - expect_redirect(:root) + expect_flash_message(:alert, I18n.t('application.not_signed_in')) + expect_redirect(:sign_in) context 'with an admin' do let(:user) { create(:admin) } expect_flash_message(:alert, I18n.t('application.not_found')) + expect_redirect(:root) end context 'with a teacher' do let(:user) { create(:teacher) } expect_flash_message(:alert, I18n.t('application.not_authorized')) + expect_redirect(:root) end end diff --git a/spec/controllers/internal_users_controller_spec.rb b/spec/controllers/internal_users_controller_spec.rb index c25944eb..6021c8cb 100644 --- a/spec/controllers/internal_users_controller_spec.rb +++ b/spec/controllers/internal_users_controller_spec.rb @@ -19,7 +19,7 @@ RSpec.describe InternalUsersController do context 'without a valid activation token' do before { get :activate, params: {id: user.id} } - expect_redirect(:root) + expect_redirect(:sign_in) end context 'with an already activated user' do @@ -28,7 +28,7 @@ RSpec.describe InternalUsersController do get :activate, params: {id: user.id, token: user.activation_token} end - expect_redirect(:root) + expect_redirect(:sign_in) end context 'with valid preconditions' do @@ -56,7 +56,7 @@ RSpec.describe InternalUsersController do context 'without a valid activation token' do before { put :activate, params: {id: user.id} } - expect_redirect(:root) + expect_redirect(:sign_in) end context 'with an already activated user' do @@ -65,7 +65,7 @@ RSpec.describe InternalUsersController do put :activate, params: {id: user.id, internal_user: {activation_token: user.activation_token, password:, password_confirmation: password}} end - expect_redirect(:root) + expect_redirect(:sign_in) end context 'without a password' do @@ -249,7 +249,7 @@ RSpec.describe InternalUsersController do context 'without a valid password reset token' do before { get :reset_password, params: {id: user.id} } - expect_redirect(:root) + expect_redirect(:sign_in) end context 'with a valid password reset token' do @@ -270,7 +270,7 @@ RSpec.describe InternalUsersController do context 'without a valid password reset token' do before { put :reset_password, params: {id: user.id} } - expect_redirect(:root) + expect_redirect(:sign_in) end context 'with a valid password reset token' do diff --git a/spec/features/authentication_spec.rb b/spec/features/authentication_spec.rb index 8ff141e1..c1848f21 100644 --- a/spec/features/authentication_spec.rb +++ b/spec/features/authentication_spec.rb @@ -3,8 +3,8 @@ require 'rails_helper' RSpec.describe 'Authentication' do - let(:user) { create(:admin) } - let(:password) { attributes_for(:admin)[:password] } + let(:user) { create(:teacher) } + let(:password) { attributes_for(:teacher)[:password] } context 'when signed out' do before { visit(root_path) } @@ -33,6 +33,38 @@ RSpec.describe 'Authentication' do end end + context 'when a restricted sub-page is opened' do + let(:exercise) { create(:math, user:, public: false) } + + before { visit(exercise_path(exercise)) } + + it 'displays a sign in link' do + expect(page).to have_content(I18n.t('sessions.new.link')) + end + + it 'shows a notification' do + expect(page).to have_content(I18n.t('application.not_signed_in')) + end + + it 'redirects to the desired page immediately after sign-in' do + fill_in('Email', with: user.email) + fill_in('Password', with: password) + click_button(I18n.t('sessions.new.link')) + expect(page).to have_content(exercise.title) + end + + context 'when a user still has no access' do + let(:exercise) { create(:math, public: false) } + + it 'informs the user about missing permissions' do + fill_in('Email', with: user.email) + fill_in('Password', with: password) + click_button(I18n.t('sessions.new.link')) + expect(page).to have_content(I18n.t('application.not_authorized')) + end + end + end + context 'with no authentication token' do let(:request_for_comment) { create(:rfc_with_comment, user:) } let(:rfc_path) { request_for_comment_url(request_for_comment) } @@ -41,8 +73,8 @@ RSpec.describe 'Authentication' do visit(rfc_path) expect(page).not_to have_current_path(rfc_path) expect(page).not_to have_content(request_for_comment.exercise.title) - expect(page).to have_current_path(root_path) - expect(page).to have_content(I18n.t('application.not_authorized')) + expect(page).to have_current_path(sign_in_path) + expect(page).to have_content(I18n.t('application.not_signed_in')) end end @@ -75,8 +107,8 @@ RSpec.describe 'Authentication' do visit(rfc_link) expect(page).not_to have_current_path(rfc_link) expect(page).not_to have_content(request_for_comment.exercise.title) - expect(page).to have_current_path(root_path) - expect(page).to have_content(I18n.t('application.not_authorized')) + expect(page).to have_current_path(sign_in_path) + expect(page).to have_content(I18n.t('application.not_signed_in')) end end @@ -95,7 +127,7 @@ RSpec.describe 'Authentication' do expect(page).to have_current_path(rfc_link) visit(sign_out_path) visit(rfc_link) - expect(page).to have_current_path(root_path) + expect(page).to have_current_path(sign_in_path) end end end