static-rails 0.0.3 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 04cd8c43f2a9d6ff2da0768d8daa7024ca470acb94a2444540d26411dad45675
4
- data.tar.gz: 55246dbc95a32e8517db8bc8b21136f9a3cac60ba18b3cd9bee27ef2501ae774
3
+ metadata.gz: 760a85803ecdc64592ce6f0f29e948fe744ff7e0d5d893f62f713f11aa7de9cb
4
+ data.tar.gz: 235ed594c1bf01dba53e028e3419ce1f5919e14de31441c5fb3f6a0b3b3de382
5
5
  SHA512:
6
- metadata.gz: e59dd52fb103bc1add1ad27a05ac83eff4b04b09ecc7bb08b73d2fd071279a87e8ae05e73c2f43a64eda4e2c1c77281d0fd8650a5c4aea2d04c2088980606127
7
- data.tar.gz: 548aa50ee427be8b0a3842b4000a07dbac7b0513cf9a712ab46dcd8903a829a58f395345549b921b154d5acdfa235fdc482e7e357ffcbdffec6a07aaf0d34319
6
+ metadata.gz: fd446ac15d01e261594e66388afada570c203046d0dcaa52437c43aca8cc8f5a09c19ce1e87aa699b7b9407a3f9993328e285aa1a4aaf53035493fd2cde7a28d
7
+ data.tar.gz: 592a87c638a0f861c673566746b591700e256de4eae4d3b28218d6faa6e994e5c3cccf589f3a2d16d850275d8d5db079661ac03aee17e6bc62ecf62b28cdc538
@@ -1,3 +1,30 @@
1
+ ## 0.0.8
2
+
3
+ * Add support for the [CSRF
4
+ changes](https://github.com/rails/rails/commit/358ff18975f26e820ea355ec113ffc5228e59af8) in Rails 6.0.3.1
5
+
6
+ ## 0.0.7
7
+
8
+ * Ensure that CSRF tokens are valid, at the cost of some performance and
9
+ reliance on additional Rails internals. As a result CSRF cookie setting is now
10
+ disabled by default [#6](https://github.com/testdouble/static-rails/pull/6)
11
+
12
+ ## 0.0.6
13
+
14
+ * Fix an issue where `ActionDispatch::FileHandler` won't be loaded in the event
15
+ that static-rails is serving compiled assets but Rails is not
16
+
17
+ ## 0.0.5
18
+
19
+ * Add a site option `compile_404_file_path` to specify a file to be used as a
20
+ 404 page when serving compiled assets and no file is found
21
+
22
+ ## 0.0.4
23
+
24
+ * Add a cookie named `_csrf_token` by default to all static site requests, so
25
+ that your static sites can make CSRF-protected requests of your server
26
+ ([#4](https://github.com/testdouble/static-rails/pull/4))
27
+
1
28
  ## 0.0.3
2
29
 
3
30
  * Add `url_skip_paths_starting_with` array of strings option to site
@@ -1,27 +1,27 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- static-rails (0.0.3)
4
+ static-rails (0.0.8)
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![CircleCI](https://circleci.com/gh/testdouble/static-rails.svg?style=svg)](https://circleci.com/gh/testdouble/static-rails)
4
4
 
5
- ## What is this thing?
5
+ ## Build and serve your static sites from your Rails app
6
6
 
7
7
  **tl;dr in development, static-rails runs your static site generators &
8
8
  proxies requests; in production, it compiles and serves the final assets**
@@ -25,18 +25,18 @@ without forcing you to abandon your monolithic Rails architecture.
25
25
 
26
26
  Here's what it does:
27
27
 
28
- * In `development` and `test` environments, the gem will run launch each site's
29
- `serve_command` and proxy to them any requests that match their configured
30
- `url_subdomain` and `url_root_path` properties
28
+ * In development, static-rails launches your sites' local servers and then
29
+ proxies any requests to wherever you've mounted them in your Rails app so you
30
+ can start a single server and transition work between your static sites and
31
+ Rails app seamlessly
31
32
 
32
- * When running `rake assets:precompile` (typically performed during a deploy),
33
- the `compile_command` you've configured will be executed for each of your
34
- sites
33
+ * When deploying, static-rails will compile all your static assets when `rake
34
+ assets:precompile` is run, meaning your assets will be built automatically
35
+ when pushed to a platform like Heroku
35
36
 
36
- * In `production`, the gem will host each of your sites' assets out of your
37
- configured `compile_dir` using the same middleware code that Rails uses to
38
- host assets out of `public/`. (Putting a performant CDN in front of everything
39
- remains an exercise for the reader.)
37
+ * In production, static-rails will serve your sites' compiled assets from disk
38
+ with a similar features and performance to what you're familiar with if you've
39
+ ever hosted files out of your `public/` directory
40
40
 
41
41
  ## Install
42
42
 
@@ -46,7 +46,7 @@ Add this to your Gemfile:
46
46
  gem "static-rails"
47
47
  ```
48
48
 
49
- Then run this generator to create a configuration file
49
+ Then run this generator to create a configuration file
50
50
  `config/initializers/static.rb`:
51
51
 
52
52
  ```
@@ -54,11 +54,110 @@ $ rails g static_rails:initializer
54
54
  ```
55
55
 
56
56
  You can check out the configuration options in the [generated file's
57
- comments](/lib/generators/templates/static.rb).
57
+ comments]().
58
58
 
59
59
  Want an example of setting things up? You're in luck, there's an [example
60
60
  app](/example) right in this repo!
61
61
 
62
+ ## Configuring the gem
63
+
64
+ **(Want to dive right in? The generated initializer [enumerates every
65
+ option](/lib/generators/templates/static.rb) and the [example app's
66
+ config](https://github.com/testdouble/static-rails/blob/master/example/config/initializers/static.rb)
67
+ sets up 4 sites.)**
68
+
69
+ ### Top-level configuration
70
+
71
+ So, what should you stick in your initializer's `StaticRails.config do |config|`
72
+ block? These options are set right off the `config` object and control the
73
+ overall behavior of the gem itself, across all your static sites:
74
+
75
+ * **config.proxy_requests** (Default: `!Rails.env.production?`) when true,
76
+ the gem's middleware requests that match where you've mounted your static site
77
+ and proxy them to the development server
78
+
79
+ * **config.serve_compiled_assets** (Default: `Rails.env.production?`) when true,
80
+ the gem's middleware will find your static assets on disk and serve them using
81
+ the same code that Rails uses to serve files out of `public/`
82
+
83
+ * **config.ping_server_timeout** (Default: `5`) the number of seconds that (when
84
+ `proxy_requests` is true, that the gem will wait for a response from a static
85
+ site's server on any given request before timing out and raising an error
86
+
87
+ * **config.set_csrf_token_cookie** (Default: `false`) when true, the gem's
88
+ middleware will set a cookie named `_csrf_token` with each request of your
89
+ static site. You can use this to set the `'x-csrf-token'` header on any
90
+ requests from your site back to routes hosted by the Rails app that are
91
+ [protected from CSRF
92
+ forgery](https://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf)
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)
95
+
96
+ ### Configuring your static sites themselves
97
+
98
+ To tell the gem about your static sites, assign an array of hashes as `sites`
99
+ (e.g. `config.sites = [{…}]`). Each of those hashes have the following options:
100
+
101
+ * **name** (Required) A unique name for the site (primarily used for
102
+ logging)
103
+
104
+ * **source_dir** (Required) The file path (relative to the Rails app's root) to
105
+ the static site's project directory
106
+
107
+ * **url_subdomain** (Default: `nil`) Constrains the static site's assets to only
108
+ be served for requests to a given subdomain (e.g. for a Rails app hosting
109
+ `example.com`, a Hugo site at `blog.example.com` would set this to `"blog"`)
110
+
111
+ * **url_root_path** (Default: `/`) The base URL path at which to mount the
112
+ static site (e.g. if you want your Jekyll site hosted at `example.com/docs`,
113
+ you'd set this to `/docs`). For most static site generators, you'll want to
114
+ configure it to serve assets from the same path so links and other references
115
+ are correct (see below for examples)
116
+
117
+ * **url_skip_paths_starting_with** (Default: `[]`) If you want to mount your
118
+ static site to `/` but allow the Rails app to serve APIs from `/api`, you can
119
+ set the path prefix `["/api"]` here to tell the gem's middleware not to try to
120
+ proxy or serve the request from your static site, but rather let Rails handle
121
+ it
122
+
123
+ * **start_server** (Default `!Rails.env.production?) When true, the gem will
124
+ start the site's server (and if it ever exits, restart it) as your Rails app
125
+ is booting up. All output from the server will be forwarded to STDOUT/STDERR
126
+
127
+ * **server_command** (Required if `start_server` is true) the command to run to
128
+ start the site's server, from the working directory of `source_dir` (e.g.
129
+ `hugo server`)
130
+
131
+ * **ping_server** (Default: true) if this and `start_server` are both true, then
132
+ wait to proxy any requests until the server is accepting TCP connections
133
+
134
+ * **env** (Default: `{}`) any environment variables you need to pass to either
135
+ the server or compile commands (e.g. `env: {"BUNDLE_PATH" =>
136
+ "vendor/bundle"}`). Note that this configuration file is Ruby, so if you need
137
+ to provide different env vars based on Rails environment, you have the power
138
+ to do that!
139
+
140
+ * **server_host** (Default: `localhost`) the host your static site's server will
141
+ run on
142
+
143
+ * **server_port** (Required if `proxy_requests` is true) the port your static
144
+ site's server will accept requests on
145
+
146
+ * **server_path** (Default: `"/"`) the root URL path to which requests should be
147
+ proxied
148
+
149
+ * **compile_comand** (Required) the command to be run by both the
150
+ `static:compile` and `assets:precompile` Rake commands (e.g. `npm run build`),
151
+ with working directory set to the site's `source_dir`
152
+
153
+ * **compile_dir** (Required when `serve_compiled_assets` is true) the root file
154
+ path to which production assets are compiled, relative to the site's
155
+ `source_dir`
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
+
62
161
  ## Configuring your static site generators
63
162
 
64
163
  Assuming you won't be mounting your static site to your app's root `/` path,
@@ -104,9 +203,10 @@ To mitigate this, there are a few things you can do:
104
203
  base path with `{{ "/" | relURL }}` (given the above `baseURL`, this will
105
204
  render `"/marketing/"`)
106
205
 
107
- Also, because Hugo will serve `/livereload.js` from the root, live-reloading probably
206
+ Also, because Hugo will serve `/livereload.js` from the root, live-reloading probably
108
207
  won't work in development when running through the static-rails proxy.
109
- You might consider disabling it with `--disableLiveReload`.
208
+ You might consider disabling it with `--disableLiveReload` unless you're serving
209
+ Hugo from a root path ("`/`").
110
210
 
111
211
  A static-rails config for a Hugo configuration in `sites` might look like:
112
212
 
@@ -9,6 +9,11 @@ StaticRails.config do |config|
9
9
  # (Applies when a site has both start_server and ping_server set to true)
10
10
  # config.ping_server_timeout = 5
11
11
 
12
+ # When true, both the proxy & static asset middleware will set a cookie
13
+ # named "_csrf_token" to the Rails CSRF token, allowing any client-side
14
+ # API requests to take advantage of Rails' request forgery protection
15
+ # config.set_csrf_token_cookie = false
16
+
12
17
  # The list of static sites you are hosting with static-rails.
13
18
  # Note that order matters! Request will be forwarded to the first site that
14
19
  # matches the subdomain and root path (this probably means you want any sites
@@ -61,6 +66,9 @@ StaticRails.config do |config|
61
66
  #
62
67
  # # The destination of production-compiled assets, relative to Rails root
63
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"
64
72
  # },
65
73
  ]
66
74
  end
@@ -13,20 +13,24 @@ 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
23
23
  attr_accessor :ping_server_timeout
24
24
 
25
+ # When true, a cookie named "_csrf_token" will be set by static-rails middleware
26
+ attr_accessor :set_csrf_token_cookie
27
+
25
28
  def initialize
26
29
  @sites = []
27
30
  @proxy_requests = !Rails.env.production?
28
31
  @serve_compiled_assets = Rails.env.production?
29
32
  @ping_server_timeout = 5
33
+ @set_csrf_token_cookie = false
30
34
  end
31
35
 
32
36
  attr_reader :sites
@@ -0,0 +1,15 @@
1
+ module StaticRails
2
+ class DeterminesWhetherToHandleRequest
3
+ def initialize
4
+ @matches_request_to_static_site = MatchesRequestToStaticSite.new
5
+ end
6
+
7
+ def call(env)
8
+ req = Rack::Request.new(env)
9
+
10
+ (req.get? || req.head?) &&
11
+ (StaticRails.config.proxy_requests || StaticRails.config.serve_compiled_assets) &&
12
+ @matches_request_to_static_site.call(req)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ module StaticRails
2
+ class GetsCsrfToken
3
+ def call(req)
4
+ masked_authenticity_token(req.session)
5
+ end
6
+
7
+ private
8
+
9
+ def csrf_token_hmac(session, identifier)
10
+ ActionController::RequestForgeryProtection.instance_method(:csrf_token_hmac).bind(self).call(session, identifier)
11
+ end
12
+
13
+ def mask_token(raw_token)
14
+ ActionController::RequestForgeryProtection.instance_method(:mask_token).bind(self).call(raw_token)
15
+ end
16
+
17
+ def masked_authenticity_token(session, form_options: {})
18
+ ActionController::RequestForgeryProtection.instance_method(:masked_authenticity_token).bind(self).call(session, form_options)
19
+ end
20
+
21
+ def global_csrf_token(session)
22
+ ActionController::RequestForgeryProtection.instance_method(:global_csrf_token).bind(self).call(session)
23
+ end
24
+
25
+ def real_csrf_token(session)
26
+ ActionController::RequestForgeryProtection.instance_method(:real_csrf_token).bind(self).call(session)
27
+ end
28
+
29
+ def xor_byte_strings(s1, s2)
30
+ ActionController::RequestForgeryProtection.instance_method(:xor_byte_strings).bind(self).call(s1, s2)
31
+ end
32
+
33
+ def per_form_csrf_tokens
34
+ false
35
+ end
36
+ end
37
+ end
@@ -14,22 +14,21 @@ module StaticRails
14
14
 
15
15
  def perform_request(env)
16
16
  return @app.call(env) unless StaticRails.config.proxy_requests
17
- ServerStore.instance.ensure_all_servers_are_started
18
17
 
19
- req = Rack::Request.new(env)
20
18
  server_store = ServerStore.instance
19
+ server_store.ensure_all_servers_are_started
21
20
  server_store.ensure_servers_are_up
22
21
 
22
+ req = Rack::Request.new(env)
23
23
  if (req.get? || req.head?) && (site = @matches_request_to_static_site.call(req))
24
24
  if site.ping_server && (server = server_store.server_for(site))
25
25
  server.wait_until_ready
26
26
  end
27
27
 
28
28
  @backend = URI("http://#{site.server_host}:#{site.server_port}")
29
-
30
29
  env["HTTP_HOST"] = @backend.host
31
30
  env["PATH_INFO"] = forwarding_path(site, req)
32
- env["HTTP_COOKIE"] = ""
31
+
33
32
  super(env)
34
33
  else
35
34
  @app.call(env)
@@ -1,7 +1,7 @@
1
1
  require_relative "rack_server_check"
2
2
  require_relative "server_store"
3
- require_relative "proxy_middleware"
4
- require_relative "static_middleware"
3
+ require_relative "site_middleware"
4
+ require_relative "site_plus_csrf_middleware"
5
5
 
6
6
  module StaticRails
7
7
  class Railtie < ::Rails::Railtie
@@ -9,14 +9,9 @@ module StaticRails
9
9
  load "tasks/static-rails.rake"
10
10
  end
11
11
 
12
- # Note that user initializer won't have run yet, but we seem to need to
13
- # register the middleware by now if it's going to properly get added to the
14
- # stack. So if the user overrides these flags' defaults, the middleware will
15
- # still be added but will be responsible itself for skipping each request
16
- if StaticRails.config.proxy_requests
17
- config.app_middleware.insert_before 0, ProxyMiddleware
18
- elsif StaticRails.config.serve_compiled_assets
19
- config.app_middleware.insert_before 0, StaticMiddleware
12
+ initializer "static_rails.middleware" do
13
+ config.app_middleware.insert_before 0, SiteMiddleware
14
+ config.app_middleware.use SitePlusCsrfMiddleware
20
15
  end
21
16
 
22
17
  config.after_initialize do |app|
@@ -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
 
@@ -0,0 +1,58 @@
1
+ require_relative "proxy_middleware"
2
+ require_relative "static_middleware"
3
+ require_relative "determines_whether_to_handle_request"
4
+
5
+ module StaticRails
6
+ class SiteMiddleware
7
+ PATH_INFO_OBFUSCATION = "JujJVj31M3SpzTjIGBJ2-3iE0lKXOIOlbLuk9Lxwe-Ll2uLuwH5KD8dmt1MqyZ"
8
+
9
+ def initialize(app)
10
+ @app = app
11
+ @proxy_middleware = ProxyMiddleware.new(app)
12
+ @static_middleware = StaticMiddleware.new(app)
13
+ @determines_whether_to_handle_request = DeterminesWhetherToHandleRequest.new
14
+ end
15
+
16
+ def call(env)
17
+ return @app.call(env) unless @determines_whether_to_handle_request.call(env)
18
+
19
+ if require_csrf_before_processing_request?
20
+ # You might be asking yourself what the hell is going on here. In short,
21
+ # This middleware sits at the top of the stack, which is too early to
22
+ # set a CSRF token in a cookie. Therefore, we've placed a subclass of
23
+ # this middleware named SitePlusCsrfMiddleware near the bottom of the
24
+ # middleware stack, which is slower but comes after Session::CookieStore
25
+ # and therefore can write _csrf_token to the cookie. As a result, the
26
+ # observable behavior to the user is identical, but the first request
27
+ # to set the cookie will be marginally slower because it needs to go
28
+ # deeper down the Rails middleware stack
29
+ #
30
+ # But! Between these two is ActionDispatch::Static. In the odd case that
31
+ # a path that this middleware would serve happens to match the name of
32
+ # a path in public/, kicking down the middleware stack would result in
33
+ # that file being served instead of our deeper middleware being called.
34
+ # So to work around this we're just making the PATH_INFO property so
35
+ # ugly that there's no chance it'll match anything. When our subclass
36
+ # gets its shot at this request, it'll know to remove the path
37
+ # obfuscation from PATH_INFO and go about its business.
38
+ #
39
+ # See, easy!
40
+ #
41
+ # (By the way, this was all Matthew Draper's bright idea. You can
42
+ # compliment him here: https://github.com/matthewd )
43
+ @app.call(env.merge("PATH_INFO" => PATH_INFO_OBFUSCATION + env["PATH_INFO"]))
44
+ elsif StaticRails.config.proxy_requests
45
+ @proxy_middleware.call(env)
46
+ elsif StaticRails.config.serve_compiled_assets
47
+ @static_middleware.call(env)
48
+ end
49
+ end
50
+
51
+ protected
52
+
53
+ # Override this in subclass since it'll call super(env) and deal itself
54
+ def require_csrf_before_processing_request?
55
+ StaticRails.config.set_csrf_token_cookie
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,50 @@
1
+ require_relative "site_middleware"
2
+ require_relative "determines_whether_to_handle_request"
3
+ require_relative "validates_csrf_token"
4
+ require_relative "gets_csrf_token"
5
+
6
+ module StaticRails
7
+ class SitePlusCsrfMiddleware < SiteMiddleware
8
+ def initialize(app)
9
+ @determines_whether_to_handle_request = DeterminesWhetherToHandleRequest.new
10
+ @validates_csrf_token = ValidatesCsrfToken.new
11
+ @gets_csrf_token = GetsCsrfToken.new
12
+ super
13
+ end
14
+
15
+ def call(env)
16
+ return @app.call(env) unless env["PATH_INFO"]&.start_with?(PATH_INFO_OBFUSCATION) || @determines_whether_to_handle_request.call(env)
17
+
18
+ env = env.merge(
19
+ "PATH_INFO" => env["PATH_INFO"].gsub(/^#{PATH_INFO_OBFUSCATION}/, "")
20
+ )
21
+ status, headers, body = super(env)
22
+
23
+ if StaticRails.config.set_csrf_token_cookie
24
+ req = Rack::Request.new(env)
25
+ res = Rack::Response.new(body, status, headers)
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
32
+ res.finish
33
+ else
34
+ [status, headers, body]
35
+ end
36
+ end
37
+
38
+ protected
39
+
40
+ def require_csrf_before_processing_request?
41
+ false
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
49
+ end
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,29 @@
1
+ module StaticRails
2
+ class ValidatesCsrfToken
3
+ def call(req)
4
+ valid_authenticity_token?(req.session, req.cookies["_csrf_token"])
5
+ end
6
+
7
+ private
8
+
9
+ [
10
+ :compare_with_global_token,
11
+ :global_csrf_token,
12
+ :csrf_token_hmac,
13
+ :valid_authenticity_token?,
14
+ :unmask_token,
15
+ :compare_with_real_token,
16
+ :valid_per_form_csrf_token?,
17
+ :xor_byte_strings,
18
+ :real_csrf_token
19
+ ].each do |method|
20
+ define_method method do |*args, **kwargs, &blk|
21
+ ActionController::RequestForgeryProtection.instance_method(method).bind(self).call(*args, **kwargs, &blk)
22
+ end
23
+ end
24
+
25
+ def per_form_csrf_tokens
26
+ false
27
+ end
28
+ end
29
+ end
@@ -1,3 +1,3 @@
1
1
  module StaticRails
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.8"
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.3
4
+ version: 0.0.8
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-11 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,9 @@ 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/determines_whether_to_handle_request.rb
65
66
  - lib/static-rails/error.rb
67
+ - lib/static-rails/gets_csrf_token.rb
66
68
  - lib/static-rails/matches_request_to_static_site.rb
67
69
  - lib/static-rails/proxy_middleware.rb
68
70
  - lib/static-rails/rack_server_check.rb
@@ -70,7 +72,10 @@ files:
70
72
  - lib/static-rails/server.rb
71
73
  - lib/static-rails/server_store.rb
72
74
  - lib/static-rails/site.rb
75
+ - lib/static-rails/site_middleware.rb
76
+ - lib/static-rails/site_plus_csrf_middleware.rb
73
77
  - lib/static-rails/static_middleware.rb
78
+ - lib/static-rails/validates_csrf_token.rb
74
79
  - lib/static-rails/version.rb
75
80
  - lib/static-rails/waits_for_connection.rb
76
81
  - lib/tasks/static-rails.rake