Skip to content

Commit

Permalink
Improve cache-control response headers on admin content.
Browse files Browse the repository at this point in the history
- Use stricter "no-store" cache-control headers for the things behind
  admin logins.
- Setup better long-expiration and immutable cache-control headers for
  the fingerprinted asset files to allow for caching.
  • Loading branch information
GUI committed Jan 24, 2018
1 parent 398aeb5 commit 2e21548
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class ApplicationController < ActionController::Base
prepend_around_filter :use_locale
protect_from_forgery :with => :exception

before_action :set_cache_control
around_action :set_userstamp

def after_sign_in_path_for(resource)
Expand Down Expand Up @@ -154,6 +155,11 @@ def verify_authenticity_token_with_admin_token
end
end

def set_cache_control
response.headers["Cache-Control"] = "no-cache, max-age=0, must-revalidate, no-store"
response.headers["Pragma"] = "no-cache"
end

def set_userstamp
orig = RequestStore.store[:current_userstamp_user]
RequestStore.store[:current_userstamp_user] = current_admin
Expand Down
3 changes: 2 additions & 1 deletion src/api-umbrella/web-app/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@
200,
{
"Content-Type" => "application/javascript",
"Cache-Control" => "max-age=0, private, no-cache, no-store, must-revalidate",
"Cache-Control" => "no-cache, max-age=0, must-revalidate, no-store",
"Pragma" => "no-cache",
},
[
script,
Expand Down
9 changes: 9 additions & 0 deletions templates/etc/nginx/router.conf.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ http {

{{^_development_env?}}
location /web-assets/ {
more_set_headers "Cache-Control: public, max-age=31536000, immutable";
alias {{_embedded_root_dir}}/apps/core/current/build/dist/web-app-assets/web-assets/;
}
{{/_development_env?}}
Expand All @@ -354,8 +355,16 @@ http {
}

rewrite ^/admin$ /admin/ permanent;
{{^_development_env?}}
location /admin/assets/ {
more_set_headers "Cache-Control: public, max-age=31536000, immutable";
alias {{_embedded_root_dir}}/apps/core/current/build/dist/admin-ui/assets/;
}
{{/_development_env?}}
location /admin/ {
{{^_development_env?}}
more_set_headers "Cache-Control: no-cache, max-age=0, must-revalidate, no-store";
more_set_headers "Pragma: no-cache";
alias {{_embedded_root_dir}}/apps/core/current/build/dist/admin-ui/;
{{/_development_env?}}
{{#_development_env?}}
Expand Down
83 changes: 83 additions & 0 deletions test/admin_ui/test_cache_control.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
require_relative "../test_helper"

class Test::AdminUi::TestCacheControl < Minitest::Test
include ApiUmbrellaTestHelpers::Setup
parallelize_me!

def setup
super
setup_server
end

def test_admin_ui
# Check the admin index.html
response = Typhoeus.get("https://127.0.0.1:9081/admin/", keyless_http_options)
assert_response_code(200, response)
assert_equal("text/html", response.headers["Content-Type"])
assert_equal("no-cache, max-age=0, must-revalidate, no-store", response.headers["Cache-Control"])
assert_equal("no-cache", response.headers["Pragma"])

# Parse the HTML page and find the JS and CSS assets.
doc = Nokogiri::HTML(response.body)
scripts = doc.xpath("//body//script[starts-with(@src, 'assets/')]")
assert_operator(scripts.length, :>=, 1)
stylesheets = doc.xpath("//head//link[starts-with(@href, 'assets/')]")
assert_operator(stylesheets.length, :>=, 1)

# Ensure that all the linked assets use fingerprinted filenames (for cache
# busting), and return long cache-control headers.
scripts.each do |script|
assert_match(%r{\Aassets/[\w-]+-\w{32}\.js\z}, script[:src])

response = Typhoeus.get("https://127.0.0.1:9081/admin/#{script[:src]}", keyless_http_options)
assert_response_code(200, response)
assert_equal("application/javascript", response.headers["Content-Type"])
assert_equal("public, max-age=31536000, immutable", response.headers["Cache-Control"])
assert_nil(response.headers["Pragma"])
end
stylesheets.each do |stylesheet|
assert_match(%r{\Aassets/[\w-]+-\w{32}\.css\z}, stylesheet[:href])

response = Typhoeus.get("https://127.0.0.1:9081/admin/#{stylesheet[:href]}", keyless_http_options)
assert_response_code(200, response)
assert_equal("text/css", response.headers["Content-Type"])
assert_equal("public, max-age=31536000, immutable", response.headers["Cache-Control"])
assert_nil(response.headers["Pragma"])
end
end

def test_admin_login
# Check the server-side page.
FactoryGirl.create(:admin)
response = Typhoeus.get("https://127.0.0.1:9081/admin/login", keyless_http_options)
assert_response_code(200, response)
assert_equal("text/html; charset=utf-8", response.headers["Content-Type"])
assert_equal("no-cache, max-age=0, must-revalidate, no-store", response.headers["Cache-Control"])
assert_equal("no-cache", response.headers["Pragma"])

# Parse the HTML page and find the CSS assets.
doc = Nokogiri::HTML(response.body)
stylesheets = doc.xpath("//head//link[starts-with(@href, '/web-assets/')]")
assert_equal(1, stylesheets.length)

# Ensure that all the linked assets use fingerprinted filenames (for cache
# busting), and return long cache-control headers.
stylesheets.each do |stylesheet|
assert_match(%r{\A/web-assets/admin/[\w-]+-\w{64}\.css\z}, stylesheet[:href])

response = Typhoeus.get("https://127.0.0.1:9081#{stylesheet[:href]}", keyless_http_options)
assert_response_code(200, response)
assert_equal("text/css", response.headers["Content-Type"])
assert_equal("public, max-age=31536000, immutable", response.headers["Cache-Control"])
assert_nil(response.headers["Pragma"])
end
end

def test_server_side_loader
response = Typhoeus.get("https://127.0.0.1:9081/admin/server_side_loader.js", keyless_http_options)
assert_response_code(200, response)
assert_equal("application/javascript", response.headers["Content-Type"])
assert_equal("no-cache, max-age=0, must-revalidate, no-store", response.headers["Cache-Control"])
assert_equal("no-cache", response.headers["Pragma"])
end
end
2 changes: 1 addition & 1 deletion test/apis/admin/test_auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def test_authenticated_no_cross_site_access
response = Typhoeus.get("https://127.0.0.1:9081/admin/auth", keyless_http_options.deep_merge(admin_session))
assert_response_code(200, response)
assert_equal("DENY", response.headers["X-Frame-Options"])
assert_equal("max-age=0, private, must-revalidate", response.headers["Cache-Control"])
assert_equal("no-cache, max-age=0, must-revalidate, no-store", response.headers["Cache-Control"])
assert_nil(response.headers["Access-Control-Allow-Credentials"])
end
end

0 comments on commit 2e21548

Please sign in to comment.