diff --git a/Gemfile b/Gemfile index 7f9d6d473..93558f6d6 100644 --- a/Gemfile +++ b/Gemfile @@ -83,6 +83,7 @@ gem "recaptcha", "~> 5.6", require: "recaptcha/rails" # Helpers for the reCAPTCH gem "redcarpet", "~> 3.5" # A fast, safe and extensible Markdown to (X)HTML parser gem "redis", "~> 4.2.2" # Redis ruby client gem "redis-rails", "~> 5.0.2" # Redis for Ruby on Rails +gem "request_store", "~> 1.5" # RequestStore gives you per-request global storage gem "reverse_markdown", "~> 2.0" # Map simple html back into markdown gem "rolify", "~> 5.3" # Very simple Roles library gem "rouge", "~> 3.24" # A pure-ruby code highlighter diff --git a/Gemfile.lock b/Gemfile.lock index 4237ae2b4..5fad30a3d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -940,6 +940,7 @@ DEPENDENCIES redcarpet (~> 3.5) redis (~> 4.2.2) redis-rails (~> 5.0.2) + request_store (~> 1.5) reverse_markdown (~> 2.0) rolify (~> 5.3) rouge (~> 3.24) diff --git a/app/assets/javascripts/initializers/initializeBaseUserData.js b/app/assets/javascripts/initializers/initializeBaseUserData.js index b279648a3..2018c345a 100644 --- a/app/assets/javascripts/initializers/initializeBaseUserData.js +++ b/app/assets/javascripts/initializers/initializeBaseUserData.js @@ -31,6 +31,10 @@ function addRelevantButtonsToArticle(user) { let actions = [ `Edit`, ]; + let clickToEditButton = document.getElementById("author-click-to-edit") + if (clickToEditButton) { + clickToEditButton.style.display = "inline-block" + } if (JSON.parse(articleContainer.dataset.published) === true) { actions.push( `Manage`, diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0da6a5b20..b978f7112 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -13,6 +13,7 @@ class ApplicationController < ActionController::Base include ImageUploads include VerifySetupCompleted include DevelopmentDependencyChecks if Rails.env.development? + include EdgeCacheSafetyCheck unless Rails.env.production? include Devise::Controllers::Rememberable rescue_from ActionView::MissingTemplate, with: :routing_error diff --git a/app/controllers/concerns/caching_headers.rb b/app/controllers/concerns/caching_headers.rb index 95e03fdf2..a0714a4d5 100644 --- a/app/controllers/concerns/caching_headers.rb +++ b/app/controllers/concerns/caching_headers.rb @@ -21,6 +21,8 @@ module CachingHeaders request.session_options[:skip] = true # no cookies + RequestStore.store[:edge_caching_in_place] = true # To be observed downstream. + response.headers["Cache-Control"] = "public, no-cache" # Used only by Fastly. response.headers["X-Accel-Expires"] = max_age.to_s # Used only by Nginx. response.headers["Surrogate-Control"] = surrogate_control.presence || build_surrogate_control( diff --git a/app/controllers/concerns/edge_cache_safety_check.rb b/app/controllers/concerns/edge_cache_safety_check.rb new file mode 100644 index 000000000..be1656b61 --- /dev/null +++ b/app/controllers/concerns/edge_cache_safety_check.rb @@ -0,0 +1,16 @@ +module EdgeCacheSafetyCheck + extend ActiveSupport::Concern + + CANNOT_USE_CURRENT_USER = "You may not use current_user in this cached code path.".freeze + + def current_user + # In production, current_user will cause a cache leak if it's placed within an edge-cached code path. + # More information here: + # https://docs.forem.com/technical-overview/architecture/#we-cache-many-content-pages-on-the-edge + return super unless RequestStore.store[:edge_caching_in_place] + + return if session_current_user_id.blank? + + CANNOT_USE_CURRENT_USER + end +end diff --git a/app/views/articles/_sidebar.html.erb b/app/views/articles/_sidebar.html.erb index 87ea929e8..88c9b13da 100644 --- a/app/views/articles/_sidebar.html.erb +++ b/app/views/articles/_sidebar.html.erb @@ -1,7 +1,7 @@