From bc0ae99a1d6d60179b4cf46361ad4d7082f5b720 Mon Sep 17 00:00:00 2001 From: Fraser Speirs Date: Mon, 9 Mar 2026 10:44:39 +0000 Subject: [PATCH 1/2] Disambiguate 401 and 403 when accessing /projects Previously, Editor-API would return a 403 error for any project access that failed, including accesses that should have recieved a 401 Unauthorised error. This change checks the state of `current_user` to decide whether to return 401 or 403. This will then allow client applications like Experience CS to properly route the user to either log in and retry or present an error page with explanations as to why the 403 occurred. --- app/controllers/api_controller.rb | 8 ++++-- spec/requests/api_controller_spec.rb | 38 ++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index d49aeae1e..29ffbb1ba 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -23,8 +23,12 @@ def authorize_user render json: { error: 'Unauthorized' }, status: :unauthorized unless current_user end - def denied(exception) - render_error_as_json(exception, :forbidden) + def denied(_exception) + if current_user + render json: { error: 'Forbidden' }, status: :forbidden + else + render json: { error: 'Unauthorized' }, status: :unauthorized + end end def not_found(exception) diff --git a/spec/requests/api_controller_spec.rb b/spec/requests/api_controller_spec.rb index ae2f8e967..d4cff479a 100644 --- a/spec/requests/api_controller_spec.rb +++ b/spec/requests/api_controller_spec.rb @@ -76,18 +76,40 @@ def index test_controller.error = CanCan::AccessDenied.new('foo') end - it 'responds with 403 Forbidden status code' do - get '/test' + context 'when current_user is not set' do + it 'responds with 401 Unauthorized status code' do + get '/test' + + expect(response).to have_http_status(:unauthorized) + end + + it 'responds with JSON including error message' do + get '/test' - expect(response).to have_http_status(:forbidden) + expect(response.parsed_body).to include( + 'error' => 'Unauthorized' + ) + end end - it 'responds with JSON including exception class & message' do - get '/test' + context 'when current_user is set' do + before do + allow(User).to receive(:from_token).and_return(User.new) + end - expect(response.parsed_body).to include( - 'error' => 'CanCan::AccessDenied: foo' - ) + it 'responds with 403 Forbidden status code' do + get '/test', headers: { 'Authorization' => 'secret' } + + expect(response).to have_http_status(:forbidden) + end + + it 'responds with JSON including error message' do + get '/test', headers: { 'Authorization' => 'secret' } + + expect(response.parsed_body).to include( + 'error' => 'Forbidden' + ) + end end end From 537729619293bc642accca816bb1e60a2f58791b Mon Sep 17 00:00:00 2001 From: Fraser Speirs Date: Mon, 9 Mar 2026 14:07:31 +0000 Subject: [PATCH 2/2] Update project Show spec for unauthorised This branch changes the response for not-logged-in users to :unauthorized instead of :forbidden. --- spec/requests/projects/show_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/requests/projects/show_spec.rb b/spec/requests/projects/show_spec.rb index 7d83dedd6..b154fc8c6 100644 --- a/spec/requests/projects/show_spec.rb +++ b/spec/requests/projects/show_spec.rb @@ -254,10 +254,10 @@ }.to_json end - it 'returns forbidden response' do + it 'returns unauthorized response' do get("/api/projects/#{project.identifier}", headers:) - expect(response).to have_http_status(:forbidden) + expect(response).to have_http_status(:unauthorized) end it 'does not return the project json' do