static-rails 0.0.4 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14d31f3821438735e307f98dd5f4a48215d0b1d678915369a7abd459b3b40145
4
- data.tar.gz: bc7b15c689693e533a75ac378db060396cde1839edb0acffc04c30e18872bf77
3
+ metadata.gz: 6745d74377412999963201634328706d263efcb29eb445a71c84cd427b618084
4
+ data.tar.gz: 9ffe84b1fc78ada36ab6699cb1826815e8da0fc0cdbe266c952f563a41c577a6
5
5
  SHA512:
6
- metadata.gz: f645e5e515d712acd3c1a81935d560d3ef0f2be3a42e5fd42af4320cf6a4ae25e14843b0c076dfda4ec5b980a12cc436cfbef527768704f97c6e89ac2e7e17f6
7
- data.tar.gz: 61600757a2e549dc5bff49b8fe01da9c0f45eac4a1fd6ba815ea4aafb5655270bfa457ac76c98a0927b98e61bcf741b40a0089f0cfb55f612ad0de386b0fa9d4
6
+ metadata.gz: 679620aed3269f571bbec98a28537a34e03240e7df57319e34e0c100b85332cdbbbc6a01cf34d62552edbbc971411a881da57a33b54ede1d64b4c0833b7fbc6a
7
+ data.tar.gz: a89bc51bded625662eb00666c0d3c2f1034f9d397a84e86d7e30136398d0e53e60d0e8287cce0cda3baea0dc5c7ef037bc629635e7c9854a58b5c8376ddafa2d
@@ -1,3 +1,31 @@
1
+ ## 0.0.9
2
+
3
+ * When using CSRF protection, the artificial path info will now be
4
+ "__static_rails__" instead of a random string, to make logs appear cleaner
5
+ * Attempt to guard against future internal changes to Rails' request forgery
6
+ protection by adding `method_missing` that calls through
7
+
8
+ ## 0.0.8
9
+
10
+ * Add support for the [CSRF
11
+ changes](https://github.com/rails/rails/commit/358ff18975f26e820ea355ec113ffc5228e59af8) in Rails 6.0.3.1
12
+
13
+ ## 0.0.7
14
+
15
+ * Ensure that CSRF tokens are valid, at the cost of some performance and
16
+ reliance on additional Rails internals. As a result CSRF cookie setting is now
17
+ disabled by default [#6](https://github.com/testdouble/static-rails/pull/6)
18
+
19
+ ## 0.0.6
20
+
21
+ * Fix an issue where `ActionDispatch::FileHandler` won't be loaded in the event
22
+ that static-rails is serving compiled assets but Rails is not
23
+
24
+ ## 0.0.5
25
+
26
+ * Add a site option `compile_404_file_path` to specify a file to be used as a
27
+ 404 page when serving compiled assets and no file is found
28
+
1
29
  ## 0.0.4
2
30
 
3
31
  * Add a cookie named `_csrf_token` by default to all static site requests, so
@@ -1,27 +1,27 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- static-rails (0.0.4)
4
+ static-rails (0.0.9)
5
5
  rack-proxy (~> 0.6)
6
6
  railties (>= 5.0.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- actionpack (6.0.3)
12
- actionview (= 6.0.3)
13
- activesupport (= 6.0.3)
11
+ actionpack (6.0.3.1)
12
+ actionview (= 6.0.3.1)
13
+ activesupport (= 6.0.3.1)
14
14
  rack (~> 2.0, >= 2.0.8)
15
15
  rack-test (>= 0.6.3)
16
16
  rails-dom-testing (~> 2.0)
17
17
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
18
- actionview (6.0.3)
19
- activesupport (= 6.0.3)
18
+ actionview (6.0.3.1)
19
+ activesupport (= 6.0.3.1)
20
20
  builder (~> 3.1)
21
21
  erubi (~> 1.4)
22
22
  rails-dom-testing (~> 2.0)
23
23
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
24
- activesupport (6.0.3)
24
+ activesupport (6.0.3.1)
25
25
  concurrent-ruby (~> 1.0, >= 1.0.2)
26
26
  i18n (>= 0.7, < 2)
27
27
  minitest (~> 5.1)
@@ -32,19 +32,18 @@ GEM
32
32
  concurrent-ruby (1.1.6)
33
33
  crass (1.0.6)
34
34
  erubi (1.9.0)
35
- i18n (1.8.2)
35
+ i18n (1.8.3)
36
36
  concurrent-ruby (~> 1.0)
37
- jaro_winkler (1.5.4)
38
37
  loofah (2.5.0)
39
38
  crass (~> 1.0.2)
40
39
  nokogiri (>= 1.5.9)
41
40
  method_source (1.0.0)
42
41
  mini_portile2 (2.4.0)
43
- minitest (5.14.0)
42
+ minitest (5.14.1)
44
43
  nokogiri (1.10.9)
45
44
  mini_portile2 (~> 2.4.0)
46
45
  parallel (1.19.1)
47
- parser (2.7.1.1)
46
+ parser (2.7.1.3)
48
47
  ast (~> 2.4.0)
49
48
  rack (2.2.2)
50
49
  rack-proxy (0.6.5)
@@ -56,34 +55,38 @@ GEM
56
55
  nokogiri (>= 1.6)
57
56
  rails-html-sanitizer (1.3.0)
58
57
  loofah (~> 2.3)
59
- railties (6.0.3)
60
- actionpack (= 6.0.3)
61
- activesupport (= 6.0.3)
58
+ railties (6.0.3.1)
59
+ actionpack (= 6.0.3.1)
60
+ activesupport (= 6.0.3.1)
62
61
  method_source
63
62
  rake (>= 0.8.7)
64
63
  thor (>= 0.20.3, < 2.0)
65
64
  rainbow (3.0.0)
66
65
  rake (13.0.1)
66
+ regexp_parser (1.7.1)
67
67
  rexml (3.2.4)
68
- rubocop (0.80.1)
69
- jaro_winkler (~> 1.5.1)
68
+ rubocop (0.85.1)
70
69
  parallel (~> 1.10)
71
70
  parser (>= 2.7.0.1)
72
71
  rainbow (>= 2.2.2, < 4.0)
72
+ regexp_parser (>= 1.7)
73
73
  rexml
74
+ rubocop-ast (>= 0.0.3)
74
75
  ruby-progressbar (~> 1.7)
75
- unicode-display_width (>= 1.4.0, < 1.7)
76
- rubocop-performance (1.5.2)
76
+ unicode-display_width (>= 1.4.0, < 2.0)
77
+ rubocop-ast (0.0.3)
78
+ parser (>= 2.7.0.1)
79
+ rubocop-performance (1.6.1)
77
80
  rubocop (>= 0.71.0)
78
81
  ruby-progressbar (1.10.1)
79
- standard (0.2.5)
80
- rubocop (~> 0.80.1)
81
- rubocop-performance (~> 1.5.2)
82
+ standard (0.4.7)
83
+ rubocop (~> 0.85.0)
84
+ rubocop-performance (~> 1.6.0)
82
85
  thor (1.0.1)
83
86
  thread_safe (0.3.6)
84
87
  tzinfo (1.2.7)
85
88
  thread_safe (~> 0.1)
86
- unicode-display_width (1.6.1)
89
+ unicode-display_width (1.7.0)
87
90
  zeitwerk (2.3.0)
88
91
 
89
92
  PLATFORMS
data/README.md CHANGED
@@ -84,13 +84,14 @@ overall behavior of the gem itself, across all your static sites:
84
84
  `proxy_requests` is true, that the gem will wait for a response from a static
85
85
  site's server on any given request before timing out and raising an error
86
86
 
87
- * **config.set_csrf_token_cookie** (Default: `true`) when true, the gem's
87
+ * **config.set_csrf_token_cookie** (Default: `false`) when true, the gem's
88
88
  middleware will set a cookie named `_csrf_token` with each request of your
89
89
  static site. You can use this to set the `'x-csrf-token'` header on any
90
90
  requests from your site back to routes hosted by the Rails app that are
91
91
  [protected from CSRF
92
92
  forgery](https://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf)
93
- (if you're not using Rails' cookie store for sessions, turn this off)
93
+ (if you're not using Rails' cookie store for sessions or you're okay with API
94
+ calls bypassing Rails CSRF, leave this off)
94
95
 
95
96
  ### Configuring your static sites themselves
96
97
 
@@ -153,6 +154,10 @@ To tell the gem about your static sites, assign an array of hashes as `sites`
153
154
  path to which production assets are compiled, relative to the site's
154
155
  `source_dir`
155
156
 
157
+ * **compile_404_file_path** (Optional) When `serve_compiled_assets` is true,
158
+ this file (relative to the `compile_dir`) will be served whenever the
159
+ request's path does not match a file on disk
160
+
156
161
  ## Configuring your static site generators
157
162
 
158
163
  Assuming you won't be mounting your static site to your app's root `/` path,
@@ -12,7 +12,7 @@ StaticRails.config do |config|
12
12
  # When true, both the proxy & static asset middleware will set a cookie
13
13
  # named "_csrf_token" to the Rails CSRF token, allowing any client-side
14
14
  # API requests to take advantage of Rails' request forgery protection
15
- # config.set_csrf_token_cookie = true
15
+ # config.set_csrf_token_cookie = false
16
16
 
17
17
  # The list of static sites you are hosting with static-rails.
18
18
  # Note that order matters! Request will be forwarded to the first site that
@@ -66,6 +66,9 @@ StaticRails.config do |config|
66
66
  #
67
67
  # # The destination of production-compiled assets, relative to Rails root
68
68
  # compile_dir: "static/blog/dist"
69
+ #
70
+ # # A 404 page to be sent when serving compiled assets and no file matches
71
+ # compile_404_file_path: "404.html"
69
72
  # },
70
73
  ]
71
74
  end
@@ -13,10 +13,10 @@ module StaticRails
13
13
  # When Rails invokes our Railtie, we'll save off a reference to the Rails app
14
14
  attr_accessor :app
15
15
 
16
- # When true, the ProxyMiddleware will be added
16
+ # When true, our middleware will proxy requests to static site servers
17
17
  attr_accessor :proxy_requests
18
18
 
19
- # When true, the StaticMiddleware will be added
19
+ # When true, our middleware will serve sites' compiled asset files
20
20
  attr_accessor :serve_compiled_assets
21
21
 
22
22
  # Number of seconds to wait on sites to confirm servers are ready
@@ -30,7 +30,7 @@ module StaticRails
30
30
  @proxy_requests = !Rails.env.production?
31
31
  @serve_compiled_assets = Rails.env.production?
32
32
  @ping_server_timeout = 5
33
- @set_csrf_token_cookie = true
33
+ @set_csrf_token_cookie = false
34
34
  end
35
35
 
36
36
  attr_reader :sites
@@ -1,15 +1,31 @@
1
+ require_relative "request_forgery_protection_fallback"
2
+
1
3
  module StaticRails
2
4
  class GetsCsrfToken
5
+ include RequestForgeryProtectionFallback
6
+
3
7
  def call(req)
4
8
  masked_authenticity_token(req.session)
5
9
  end
6
10
 
7
11
  private
8
12
 
13
+ def csrf_token_hmac(session, identifier)
14
+ ActionController::RequestForgeryProtection.instance_method(:csrf_token_hmac).bind(self).call(session, identifier)
15
+ end
16
+
17
+ def mask_token(raw_token)
18
+ ActionController::RequestForgeryProtection.instance_method(:mask_token).bind(self).call(raw_token)
19
+ end
20
+
9
21
  def masked_authenticity_token(session, form_options: {})
10
22
  ActionController::RequestForgeryProtection.instance_method(:masked_authenticity_token).bind(self).call(session, form_options)
11
23
  end
12
24
 
25
+ def global_csrf_token(session)
26
+ ActionController::RequestForgeryProtection.instance_method(:global_csrf_token).bind(self).call(session)
27
+ end
28
+
13
29
  def real_csrf_token(session)
14
30
  ActionController::RequestForgeryProtection.instance_method(:real_csrf_token).bind(self).call(session)
15
31
  end
@@ -0,0 +1,19 @@
1
+ module StaticRails
2
+ module RequestForgeryProtectionFallback
3
+ def method_missing(method_name, *args, **kwargs, &blk)
4
+ if respond_to?(method_name)
5
+ ActionController::RequestForgeryProtection.instance_method(method_name).bind(self).call(*args, **kwargs, &blk)
6
+ else
7
+ super
8
+ end
9
+ end
10
+
11
+ def respond_to?(method_name, *args)
12
+ ActionController::RequestForgeryProtection.instance_method(method_name) || super
13
+ end
14
+
15
+ def respond_to_missing?(method_name, *args)
16
+ ActionController::RequestForgeryProtection.instance_method(method_name) || super
17
+ end
18
+ end
19
+ end
@@ -14,6 +14,7 @@ module StaticRails
14
14
  :server_path,
15
15
  :compile_command,
16
16
  :compile_dir,
17
+ :compile_404_file_path,
17
18
  keyword_init: true
18
19
  )
19
20
 
@@ -1,11 +1,10 @@
1
1
  require_relative "proxy_middleware"
2
2
  require_relative "static_middleware"
3
3
  require_relative "determines_whether_to_handle_request"
4
- require_relative "gets_csrf_token"
5
4
 
6
5
  module StaticRails
7
6
  class SiteMiddleware
8
- PATH_INFO_OBFUSCATION = "JujJVj31M3SpzTjIGBJ2-3iE0lKXOIOlbLuk9Lxwe-Ll2uLuwH5KD8dmt1MqyZ"
7
+ PATH_INFO_OBFUSCATION = "__static-rails__"
9
8
 
10
9
  def initialize(app)
11
10
  @app = app
@@ -17,7 +16,7 @@ module StaticRails
17
16
  def call(env)
18
17
  return @app.call(env) unless @determines_whether_to_handle_request.call(env)
19
18
 
20
- if require_csrf_before_processing_request? && !csrf_token_is_set?(env)
19
+ if require_csrf_before_processing_request?
21
20
  # You might be asking yourself what the hell is going on here. In short,
22
21
  # This middleware sits at the top of the stack, which is too early to
23
22
  # set a CSRF token in a cookie. Therefore, we've placed a subclass of
@@ -41,7 +40,7 @@ module StaticRails
41
40
  #
42
41
  # (By the way, this was all Matthew Draper's bright idea. You can
43
42
  # compliment him here: https://github.com/matthewd )
44
- @app.call(env.merge("PATH_INFO" => env["PATH_INFO"] + PATH_INFO_OBFUSCATION))
43
+ @app.call(env.merge("PATH_INFO" => PATH_INFO_OBFUSCATION + env["PATH_INFO"]))
45
44
  elsif StaticRails.config.proxy_requests
46
45
  @proxy_middleware.call(env)
47
46
  elsif StaticRails.config.serve_compiled_assets
@@ -55,9 +54,5 @@ module StaticRails
55
54
  def require_csrf_before_processing_request?
56
55
  StaticRails.config.set_csrf_token_cookie
57
56
  end
58
-
59
- def csrf_token_is_set?(env)
60
- Rack::Request.new(env).cookies.has_key?("_csrf_token")
61
- end
62
57
  end
63
58
  end
@@ -1,30 +1,34 @@
1
1
  require_relative "site_middleware"
2
2
  require_relative "determines_whether_to_handle_request"
3
+ require_relative "validates_csrf_token"
3
4
  require_relative "gets_csrf_token"
4
5
 
5
6
  module StaticRails
6
7
  class SitePlusCsrfMiddleware < SiteMiddleware
7
8
  def initialize(app)
8
9
  @determines_whether_to_handle_request = DeterminesWhetherToHandleRequest.new
10
+ @validates_csrf_token = ValidatesCsrfToken.new
9
11
  @gets_csrf_token = GetsCsrfToken.new
10
12
  super
11
13
  end
12
14
 
13
15
  def call(env)
14
- return @app.call(env) unless @determines_whether_to_handle_request.call(env)
16
+ return @app.call(env) unless env["PATH_INFO"]&.start_with?(PATH_INFO_OBFUSCATION) || @determines_whether_to_handle_request.call(env)
15
17
 
16
18
  env = env.merge(
17
- "PATH_INFO" => env["PATH_INFO"].gsub(/#{PATH_INFO_OBFUSCATION}/, "")
19
+ "PATH_INFO" => env["PATH_INFO"].gsub(/^#{PATH_INFO_OBFUSCATION}/, "")
18
20
  )
19
21
  status, headers, body = super(env)
20
22
 
21
23
  if StaticRails.config.set_csrf_token_cookie
22
24
  req = Rack::Request.new(env)
23
25
  res = Rack::Response.new(body, status, headers)
24
- res.set_cookie("_csrf_token", {
25
- value: @gets_csrf_token.call(req),
26
- path: "/"
27
- })
26
+ if needs_new_csrf_token?(req)
27
+ res.set_cookie("_csrf_token", {
28
+ value: @gets_csrf_token.call(req),
29
+ path: "/"
30
+ })
31
+ end
28
32
  res.finish
29
33
  else
30
34
  [status, headers, body]
@@ -36,5 +40,11 @@ module StaticRails
36
40
  def require_csrf_before_processing_request?
37
41
  false
38
42
  end
43
+
44
+ private
45
+
46
+ def needs_new_csrf_token?(req)
47
+ !req.cookies.has_key?("_csrf_token") || !@validates_csrf_token.call(req)
48
+ end
39
49
  end
40
50
  end
@@ -1,4 +1,5 @@
1
1
  require "rack-proxy"
2
+ require "action_dispatch/middleware/static"
2
3
 
3
4
  require_relative "matches_request_to_static_site"
4
5
 
@@ -17,7 +18,7 @@ module StaticRails
17
18
  if (req.get? || req.head?) && (site = @matches_request_to_static_site.call(req))
18
19
  file_handler = file_handler_for(site)
19
20
  path = req.path_info.gsub(/^#{site.url_root_path}/, "").chomp("/")
20
- if (match = file_handler.match?(path))
21
+ if (match = matching_file_for(file_handler, site, path))
21
22
  req.path_info = match
22
23
  return file_handler.serve(req)
23
24
  end
@@ -35,5 +36,13 @@ module StaticRails
35
36
  StaticRails.config.app.root.join(site.compile_dir).to_s
36
37
  )
37
38
  end
39
+
40
+ def matching_file_for(file_handler, site, path)
41
+ if (match = file_handler.match?(path))
42
+ match
43
+ elsif site.compile_404_file_path.present?
44
+ file_handler.match?(site.compile_404_file_path)
45
+ end
46
+ end
38
47
  end
39
48
  end
@@ -0,0 +1,33 @@
1
+ require_relative "request_forgery_protection_fallback"
2
+
3
+ module StaticRails
4
+ class ValidatesCsrfToken
5
+ include RequestForgeryProtectionFallback
6
+
7
+ def call(req)
8
+ valid_authenticity_token?(req.session, req.cookies["_csrf_token"])
9
+ end
10
+
11
+ private
12
+
13
+ [
14
+ :compare_with_global_token,
15
+ :global_csrf_token,
16
+ :csrf_token_hmac,
17
+ :valid_authenticity_token?,
18
+ :unmask_token,
19
+ :compare_with_real_token,
20
+ :valid_per_form_csrf_token?,
21
+ :xor_byte_strings,
22
+ :real_csrf_token
23
+ ].each do |method|
24
+ define_method method do |*args, **kwargs, &blk|
25
+ ActionController::RequestForgeryProtection.instance_method(method).bind(self).call(*args, **kwargs, &blk)
26
+ end
27
+ end
28
+
29
+ def per_form_csrf_tokens
30
+ false
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,3 @@
1
1
  module StaticRails
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.9"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: static-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-05-13 00:00:00.000000000 Z
11
+ date: 2020-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -62,7 +62,6 @@ files:
62
62
  - lib/static-rails.rb
63
63
  - lib/static-rails/compile.rb
64
64
  - lib/static-rails/configuration.rb
65
- - lib/static-rails/csrf_middleware.rb
66
65
  - lib/static-rails/determines_whether_to_handle_request.rb
67
66
  - lib/static-rails/error.rb
68
67
  - lib/static-rails/gets_csrf_token.rb
@@ -70,12 +69,14 @@ files:
70
69
  - lib/static-rails/proxy_middleware.rb
71
70
  - lib/static-rails/rack_server_check.rb
72
71
  - lib/static-rails/railtie.rb
72
+ - lib/static-rails/request_forgery_protection_fallback.rb
73
73
  - lib/static-rails/server.rb
74
74
  - lib/static-rails/server_store.rb
75
75
  - lib/static-rails/site.rb
76
76
  - lib/static-rails/site_middleware.rb
77
77
  - lib/static-rails/site_plus_csrf_middleware.rb
78
78
  - lib/static-rails/static_middleware.rb
79
+ - lib/static-rails/validates_csrf_token.rb
79
80
  - lib/static-rails/version.rb
80
81
  - lib/static-rails/waits_for_connection.rb
81
82
  - lib/tasks/static-rails.rake
@@ -1,20 +0,0 @@
1
- require_relative "gets_csrf_token"
2
-
3
- module StaticRails
4
- class CsrfMiddleware
5
- def initialize(app)
6
- @app = app
7
- @gets_csrf_token = GetsCsrfToken.new
8
- end
9
-
10
- def call(env)
11
- if env["__static_rails_evil_request_for_csrf_token"]
12
- req = Rack::Request.new(env)
13
- [200, {}, [@gets_csrf_token.call(req)]].tap do
14
- end
15
- else
16
- @app.call(env)
17
- end
18
- end
19
- end
20
- end