From c2f3fcca2c7e95c664d3732dd2f4662972549599 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 21:38:11 +0000 Subject: [PATCH 1/3] Initial plan From 8301fd3d461752b014f608bcb29803bccc41d92f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 Aug 2025 21:43:21 +0000 Subject: [PATCH 2/3] Add explicit CORS support for data endpoint Co-authored-by: RogerPodacter <59190+RogerPodacter@users.noreply.github.com> --- app/controllers/ethscriptions_controller.rb | 23 +++++++++++ config/routes.rb | 1 + spec/requests/cors_spec.rb | 42 +++++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 spec/requests/cors_spec.rb diff --git a/app/controllers/ethscriptions_controller.rb b/app/controllers/ethscriptions_controller.rb index 4221548..845c07a 100644 --- a/app/controllers/ethscriptions_controller.rb +++ b/app/controllers/ethscriptions_controller.rb @@ -120,12 +120,25 @@ def data blockhash, block_number = scope.pick(:block_blockhash, :block_number) unless blockhash.present? + # Ensure CORS headers are set even for 404 responses + if request.headers['Origin'] + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS' + response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization' + end head 404 return end response.headers.delete('X-Frame-Options') + # Ensure CORS headers are set for cross-origin requests + if request.headers['Origin'] + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS' + response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization' + end + set_cache_control_headers( max_age: 6, s_max_age: 1.minute, @@ -140,6 +153,16 @@ def data end end + def data_options + # Handle CORS preflight requests for the data endpoint + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS' + response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization' + response.headers['Access-Control-Max-Age'] = '3600' + + head :ok + end + def attachment scope = Ethscription.all diff --git a/config/routes.rb b/config/routes.rb index 0aafc24..cdec285 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,6 +3,7 @@ def draw_routes resources :ethscriptions, only: [:index, :show] do collection do get "/:id/data", to: "ethscriptions#data" + match "/:id/data", to: "ethscriptions#data_options", via: :options # get "/newer_ethscriptions", to: "ethscriptions#newer_ethscriptions" # get "/newer", to: "ethscriptions#newer_ethscriptions" get '/owned_by/:owned_by_address', to: 'ethscriptions#index' diff --git a/spec/requests/cors_spec.rb b/spec/requests/cors_spec.rb new file mode 100644 index 0000000..7f2417e --- /dev/null +++ b/spec/requests/cors_spec.rb @@ -0,0 +1,42 @@ +require 'rails_helper' + +RSpec.describe 'CORS for Data Endpoint', type: :request do + describe 'Ethscriptions data endpoint CORS handling' do + context 'OPTIONS preflight request' do + it 'responds with appropriate CORS headers for preflight request' do + options "/ethscriptions/1/data", + headers: { + 'Origin' => 'https://example.com', + 'Access-Control-Request-Method' => 'GET', + 'Access-Control-Request-Headers' => 'content-type' + } + + expect(response.status).to eq(200) + expect(response.headers['Access-Control-Allow-Origin']).to eq('*') + expect(response.headers['Access-Control-Allow-Methods']).to eq('GET, OPTIONS') + expect(response.headers['Access-Control-Allow-Headers']).to eq('Origin, Content-Type, Accept, Authorization') + expect(response.headers['Access-Control-Max-Age']).to eq('3600') + end + end + + context 'GET request with Origin header' do + it 'includes CORS headers when Origin header is present' do + # Note: This test may fail if ethscription #1 doesn't exist, but the CORS headers should still be set + get "/ethscriptions/1/data", + headers: { 'Origin' => 'https://example.com' } + + # Check CORS headers are present regardless of whether the ethscription exists + expect(response.headers['Access-Control-Allow-Origin']).to eq('*') + expect(response.headers['Access-Control-Allow-Methods']).to eq('GET, OPTIONS') + expect(response.headers['Access-Control-Allow-Headers']).to eq('Origin, Content-Type, Accept, Authorization') + end + + it 'does not include CORS headers when Origin header is not present' do + get "/ethscriptions/1/data" + + # CORS headers should not be set when no Origin header is present + expect(response.headers['Access-Control-Allow-Origin']).to be_nil + end + end + end +end \ No newline at end of file From 49015c0f7b06a89716af185d71ab449b3de970de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:01:40 +0000 Subject: [PATCH 3/3] Add Cross-Origin-Resource-Policy header and global CORS support for all API endpoints Co-authored-by: RogerPodacter <59190+RogerPodacter@users.noreply.github.com> --- app/controllers/application_controller.rb | 14 +++++ app/controllers/ethscriptions_controller.rb | 29 ++++++++++ config/routes.rb | 1 + spec/requests/cors_spec.rb | 60 ++++++++++++++++++++- 4 files changed, 103 insertions(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 855a724..88df93f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,17 @@ class ApplicationController < ActionController::API include FacetRailsCommon::ApplicationControllerMethods + + # Ensure all API endpoints support CORS when Origin header is present + before_action :set_cors_headers + + private + + def set_cors_headers + if request.headers['Origin'] + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD' + response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization, X-Requested-With' + response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin' + end + end end diff --git a/app/controllers/ethscriptions_controller.rb b/app/controllers/ethscriptions_controller.rb index 845c07a..ddb3787 100644 --- a/app/controllers/ethscriptions_controller.rb +++ b/app/controllers/ethscriptions_controller.rb @@ -125,6 +125,7 @@ def data response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization' + response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin' end head 404 return @@ -137,6 +138,7 @@ def data response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization' + response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin' end set_cache_control_headers( @@ -159,6 +161,7 @@ def data_options response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization' response.headers['Access-Control-Max-Age'] = '3600' + response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin' head :ok end @@ -177,12 +180,27 @@ def attachment attachment_scope = EthscriptionAttachment.where(sha: sha) unless attachment_scope.exists? + # Ensure CORS headers are set even for 404 responses + if request.headers['Origin'] + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS' + response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization' + response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin' + end head 404 return end response.headers.delete('X-Frame-Options') + # Ensure CORS headers are set for cross-origin requests + if request.headers['Origin'] + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS' + response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization' + response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin' + end + set_cache_control_headers( max_age: 6, s_max_age: 1.minute, @@ -195,6 +213,17 @@ def attachment end end + def attachment_options + # Handle CORS preflight requests for the attachment endpoint + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS' + response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization' + response.headers['Access-Control-Max-Age'] = '3600' + response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin' + + head :ok + end + def exists existing = Ethscription.find_by_content_sha(params[:sha]) diff --git a/config/routes.rb b/config/routes.rb index cdec285..169bb32 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,6 +13,7 @@ def draw_routes member do get 'attachment', to: 'ethscriptions#attachment' + match 'attachment', to: 'ethscriptions#attachment_options', via: :options end end diff --git a/spec/requests/cors_spec.rb b/spec/requests/cors_spec.rb index 7f2417e..04f4370 100644 --- a/spec/requests/cors_spec.rb +++ b/spec/requests/cors_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe 'CORS for Data Endpoint', type: :request do +RSpec.describe 'CORS for Data and Attachment Endpoints', type: :request do describe 'Ethscriptions data endpoint CORS handling' do context 'OPTIONS preflight request' do it 'responds with appropriate CORS headers for preflight request' do @@ -16,6 +16,7 @@ expect(response.headers['Access-Control-Allow-Methods']).to eq('GET, OPTIONS') expect(response.headers['Access-Control-Allow-Headers']).to eq('Origin, Content-Type, Accept, Authorization') expect(response.headers['Access-Control-Max-Age']).to eq('3600') + expect(response.headers['Cross-Origin-Resource-Policy']).to eq('cross-origin') end end @@ -29,6 +30,7 @@ expect(response.headers['Access-Control-Allow-Origin']).to eq('*') expect(response.headers['Access-Control-Allow-Methods']).to eq('GET, OPTIONS') expect(response.headers['Access-Control-Allow-Headers']).to eq('Origin, Content-Type, Accept, Authorization') + expect(response.headers['Cross-Origin-Resource-Policy']).to eq('cross-origin') end it 'does not include CORS headers when Origin header is not present' do @@ -39,4 +41,60 @@ end end end + + describe 'Ethscriptions attachment endpoint CORS handling' do + context 'OPTIONS preflight request' do + it 'responds with appropriate CORS headers for preflight request' do + options "/ethscriptions/1/attachment", + headers: { + 'Origin' => 'https://example.com', + 'Access-Control-Request-Method' => 'GET', + 'Access-Control-Request-Headers' => 'content-type' + } + + expect(response.status).to eq(200) + expect(response.headers['Access-Control-Allow-Origin']).to eq('*') + expect(response.headers['Access-Control-Allow-Methods']).to eq('GET, OPTIONS') + expect(response.headers['Access-Control-Allow-Headers']).to eq('Origin, Content-Type, Accept, Authorization') + expect(response.headers['Access-Control-Max-Age']).to eq('3600') + expect(response.headers['Cross-Origin-Resource-Policy']).to eq('cross-origin') + end + end + + context 'GET request with Origin header' do + it 'includes CORS headers when Origin header is present' do + # Note: This test may fail if ethscription #1 doesn't exist, but the CORS headers should still be set + get "/ethscriptions/1/attachment", + headers: { 'Origin' => 'https://example.com' } + + # Check CORS headers are present regardless of whether the ethscription exists + expect(response.headers['Access-Control-Allow-Origin']).to eq('*') + expect(response.headers['Access-Control-Allow-Methods']).to eq('GET, OPTIONS') + expect(response.headers['Access-Control-Allow-Headers']).to eq('Origin, Content-Type, Accept, Authorization') + expect(response.headers['Cross-Origin-Resource-Policy']).to eq('cross-origin') + end + end + end + + describe 'Global CORS handling for other endpoints' do + context 'GET request to index endpoint with Origin header' do + it 'includes global CORS headers when Origin header is present' do + get "/ethscriptions", + headers: { 'Origin' => 'https://example.com' } + + # Check global CORS headers are present + expect(response.headers['Access-Control-Allow-Origin']).to eq('*') + expect(response.headers['Access-Control-Allow-Methods']).to eq('GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD') + expect(response.headers['Access-Control-Allow-Headers']).to eq('Origin, Content-Type, Accept, Authorization, X-Requested-With') + expect(response.headers['Cross-Origin-Resource-Policy']).to eq('cross-origin') + end + + it 'does not include CORS headers when Origin header is not present' do + get "/ethscriptions" + + # CORS headers should not be set when no Origin header is present + expect(response.headers['Access-Control-Allow-Origin']).to be_nil + end + end + end end \ No newline at end of file