diff --git a/app/controllers/user_avatars_controller.rb b/app/controllers/user_avatars_controller.rb index 3f148cb477a..7a525359fd4 100644 --- a/app/controllers/user_avatars_controller.rb +++ b/app/controllers/user_avatars_controller.rb @@ -125,6 +125,8 @@ class UserAvatarsController < ApplicationController if optimized.local? optimized_path = Discourse.store.path_for(optimized) image = optimized_path if File.exist?(optimized_path) + elsif GlobalSetting.redirect_avatar_requests + return redirect_s3_avatar(Discourse.store.cdn_url(optimized.url)) else return proxy_avatar(Discourse.store.cdn_url(optimized.url), upload.created_at) end @@ -179,6 +181,11 @@ class UserAvatarsController < ApplicationController send_file path, disposition: nil end + def redirect_s3_avatar(url) + immutable_for 1.hour + redirect_to url, allow_other_host: true + end + # this protects us from a DoS def render_blank path = Rails.root + "public/images/avatar.png" diff --git a/config/discourse_defaults.conf b/config/discourse_defaults.conf index f2b84768c7a..d45d0d1f4fa 100644 --- a/config/discourse_defaults.conf +++ b/config/discourse_defaults.conf @@ -359,3 +359,6 @@ long_polling_interval = # Moves asset preloading from tags in the response document head to response headers preload_link_header = false + +# When using an external upload store, redirect `user_avatar` requests instead of proxying +redirect_avatar_requests = false diff --git a/spec/requests/user_avatars_controller_spec.rb b/spec/requests/user_avatars_controller_spec.rb index 9a59f660e34..4ed33e3b821 100644 --- a/spec/requests/user_avatars_controller_spec.rb +++ b/spec/requests/user_avatars_controller_spec.rb @@ -100,6 +100,33 @@ RSpec.describe UserAvatarsController do expect(response).to redirect_to("http://awesome.com/boom/user_avatar/default/#{user.encoded_username(lower: true)}/98/#{upload.id}_#{OptimizedImage::VERSION}.png") end + it "redirects to external store when enabled" do + global_setting :redirect_avatar_requests, true + setup_s3 + SiteSetting.avatar_sizes = "100|49" + SiteSetting.s3_cdn_url = "https://s3-cdn.example.com" + set_cdn_url("https://app-cdn.example.com") + + upload = Fabricate(:upload, url: "//#{SiteSetting.s3_upload_bucket}.s3.dualstack.us-west-1.amazonaws.com/upload/path") + + optimized_image = Fabricate(:optimized_image, + sha1: SecureRandom.hex << "A" * 8, + upload: upload, + width: 98, + height: 98, + url: "//#{SiteSetting.s3_upload_bucket}.s3.dualstack.us-west-1.amazonaws.com/optimized/path", + version: OptimizedImage::VERSION + ) + + user = Fabricate(:user, uploaded_avatar_id: upload.id) + + get "/user_avatar/default/#{user.username}/98/#{upload.id}.png" + + expect(response.status).to eq(302) + expect(response.location).to eq("https://s3-cdn.example.com/optimized/path") + expect(response.headers["Cache-Control"]).to eq('max-age=31556952, public, immutable') + end + it 'serves new version for old urls' do user = Fabricate(:user) SiteSetting.avatar_sizes = "45"