diff --git a/config/initializers/008-rack-cors.rb b/config/initializers/008-rack-cors.rb
index fa226a85a77..d0c3ce46dbf 100644
--- a/config/initializers/008-rack-cors.rb
+++ b/config/initializers/008-rack-cors.rb
@@ -6,7 +6,7 @@ class Discourse::Cors
def initialize(app, options = nil)
@app = app
if GlobalSetting.enable_cors && GlobalSetting.cors_origin.present?
- @global_origins = GlobalSetting.cors_origin.split(',').map(&:strip)
+ @global_origins = GlobalSetting.cors_origin.split(',').map { |x| x.strip.chomp('/') }
end
end
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index d5bb5e39f16..e05bf4ff74c 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -199,6 +199,7 @@ en:
second_factor_cannot_be_enforced_with_sso_enabled: "You cannot enforce 2FA if SSO is enabled."
local_login_cannot_be_disabled_if_second_factor_enforced: "You cannot disable local login if 2FA is enforced. Disable enforced 2FA before disabling local logins."
cannot_enable_s3_uploads_when_s3_enabled_globally: "You cannot enable S3 uploads because S3 uploads are already globally enabled, and enabling this site-level could cause critical issues with uploads"
+ cors_origins_should_not_have_trailing_slash: "You should not add the trailing slash (/) to CORS origins."
conflicting_google_user_id: 'The Google Account ID for this account has changed; staff intervention is required for security reasons. Please contact staff and point them to
https://meta.discourse.org/t/76575'
activemodel:
diff --git a/lib/site_settings/validations.rb b/lib/site_settings/validations.rb
index 6a1f9958c0e..6d132195f8d 100644
--- a/lib/site_settings/validations.rb
+++ b/lib/site_settings/validations.rb
@@ -191,6 +191,12 @@ module SiteSettings::Validations
validate_error :local_login_cannot_be_disabled_if_second_factor_enforced
end
+ def validate_cors_origins(new_val)
+ return if new_val.blank?
+ return unless new_val.split('|').any?(/\/$/)
+ validate_error :cors_origins_should_not_have_trailing_slash
+ end
+
private
def validate_bucket_setting(setting_name, upload_bucket, backup_bucket)
diff --git a/spec/components/hijack_spec.rb b/spec/components/hijack_spec.rb
index 93bf9c98bf3..0ed3b0b4037 100644
--- a/spec/components/hijack_spec.rb
+++ b/spec/components/hijack_spec.rb
@@ -116,6 +116,43 @@ describe Hijack do
expect(headers).to eq(expected)
end
+ it "removes trailing slash in cors origin" do
+ GlobalSetting.stubs(:enable_cors).returns(true)
+ GlobalSetting.stubs(:cors_origin).returns("https://www.rainbows.com/")
+
+ app = lambda do |env|
+ tester = Hijack::Tester.new(env)
+ tester.hijack_test do
+ render body: "hello", status: 201
+ end
+
+ expect(tester.io.string).to include("Access-Control-Allow-Origin: https://www.rainbows.com")
+ end
+
+ env = {}
+ middleware = Discourse::Cors.new(app)
+ middleware.call(env)
+
+ # it can do pre-flight
+ env = {
+ 'REQUEST_METHOD' => 'OPTIONS',
+ 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' => 'GET'
+ }
+
+ status, headers, _body = middleware.call(env)
+
+ expect(status).to eq(200)
+
+ expected = {
+ "Access-Control-Allow-Origin" => "https://www.rainbows.com",
+ "Access-Control-Allow-Headers" => "Content-Type, Cache-Control, X-Requested-With, X-CSRF-Token, Discourse-Present, User-Api-Key, User-Api-Client-Id, Authorization",
+ "Access-Control-Allow-Credentials" => "true",
+ "Access-Control-Allow-Methods" => "POST, PUT, GET, OPTIONS, DELETE"
+ }
+
+ expect(headers).to eq(expected)
+ end
+
it "handles transfers headers" do
tester.response.headers["Hello-World"] = "sam"
tester.hijack_test do
diff --git a/spec/lib/site_settings/validations_spec.rb b/spec/lib/site_settings/validations_spec.rb
index 63aeaa1252a..87ee9b99b6c 100644
--- a/spec/lib/site_settings/validations_spec.rb
+++ b/spec/lib/site_settings/validations_spec.rb
@@ -195,6 +195,16 @@ describe SiteSettings::Validations do
end
end
+ describe "#validate_cors_origins" do
+ let(:error_message) { I18n.t("errors.site_settings.cors_origins_should_not_have_trailing_slash") }
+
+ context "when the new value has trailing slash" do
+ it "should raise an error" do
+ expect { subject.validate_cors_origins("https://www.rainbows.com/") }.to raise_error(Discourse::InvalidParameters, error_message)
+ end
+ end
+ end
+
describe "#validate_secure_media" do
let(:error_message) { I18n.t("errors.site_settings.secure_media_requirements") }