static-rails 0.0.2 → 0.0.7

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: 59b735436f8beadbe02b488f6d9ba385beb4527cc3b1da082e4e07f2a4645dc9
4
- data.tar.gz: 5c40b3b20ea44e52dabef6d8660aaf59784f198759baac9e909ba9283916a9f8
3
+ metadata.gz: 71c1a0e6ce72ea1feb691e3cbfae0bec8bf723f1512cda383aeae57d56fd9765
4
+ data.tar.gz: 4d8d3c148661b2498ccfeb3677553e94a91c2cb68b5c48ba47aaace89be8adfe
5
5
  SHA512:
6
- metadata.gz: 2d222bec9bcc9c02e3c52adadcd8426290dccbd18aaff465723ed4b152147d42c4980bf34284669cbd58ec2b9794aaf02b3e719bb1b76324ffceb24547b6afbc
7
- data.tar.gz: c42139bb13e835326dd49c37f569c816689eff8a490befba9f6b37491e0113992dcaf4f5395fb24aaea20143c7abeacd5db8b0276932748ae7fc1476566263e3
6
+ metadata.gz: cc39f818e745f67930b8821dbf3b38cdfd34d33accd25979801a4fbc34df67cdf600e517a2fe806f6094e43bc2e797fcef2aa4b1bb7006f3d75317a26b19ea82
7
+ data.tar.gz: c0879dc6ef5950806aaa662035ac7593a78c334dc578f65bd5e13526d1f4c770a08b9cf1f2b8fc8e21a9d00372876736924e05687e4d33ebbb780acc5b802ea1
@@ -0,0 +1,35 @@
1
+ ## 0.0.7
2
+
3
+ * Ensure that CSRF tokens are valid, at the cost of some performance and
4
+ reliance on additional Rails internals. As a result CSRF cookie setting is now
5
+ disabled by default [#6](https://github.com/testdouble/static-rails/pull/6)
6
+
7
+ ## 0.0.6
8
+
9
+ * Fix an issue where `ActionDispatch::FileHandler` won't be loaded in the event
10
+ that static-rails is serving compiled assets but Rails is not
11
+
12
+ ## 0.0.5
13
+
14
+ * Add a site option `compile_404_file_path` to specify a file to be used as a
15
+ 404 page when serving compiled assets and no file is found
16
+
17
+ ## 0.0.4
18
+
19
+ * Add a cookie named `_csrf_token` by default to all static site requests, so
20
+ that your static sites can make CSRF-protected requests of your server
21
+ ([#4](https://github.com/testdouble/static-rails/pull/4))
22
+
23
+ ## 0.0.3
24
+
25
+ * Add `url_skip_paths_starting_with` array of strings option to site
26
+ configuration. Will fall through to the next matching site or all the way to
27
+ the Rails app.
28
+
29
+ ## 0.0.2
30
+
31
+ * Add `env` hash option to site configuration
32
+
33
+ ## 0.0.1
34
+
35
+ * Initial release
@@ -1,32 +1,32 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- static-rails (0.0.2)
4
+ static-rails (0.0.7)
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.2.2)
12
- actionview (= 6.0.2.2)
13
- activesupport (= 6.0.2.2)
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.2.2)
19
- activesupport (= 6.0.2.2)
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.2.2)
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)
28
28
  tzinfo (~> 1.1)
29
- zeitwerk (~> 2.2)
29
+ zeitwerk (~> 2.2, >= 2.2.2)
30
30
  ast (2.4.0)
31
31
  builder (3.2.4)
32
32
  concurrent-ruby (1.1.6)
@@ -56,9 +56,9 @@ GEM
56
56
  nokogiri (>= 1.6)
57
57
  rails-html-sanitizer (1.3.0)
58
58
  loofah (~> 2.3)
59
- railties (6.0.2.2)
60
- actionpack (= 6.0.2.2)
61
- activesupport (= 6.0.2.2)
59
+ railties (6.0.3.1)
60
+ actionpack (= 6.0.3.1)
61
+ activesupport (= 6.0.3.1)
62
62
  method_source
63
63
  rake (>= 0.8.7)
64
64
  thor (>= 0.20.3, < 2.0)
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
@@ -28,6 +33,9 @@ StaticRails.config do |config|
28
33
  # # Mount the static site web hosting to a certain sub-path (e.g. "/docs")
29
34
  # url_root_path: "/",
30
35
  #
36
+ # # Don't serve/redirect routes whose paths start with these strings
37
+ # url_skip_paths_starting_with: ["/api"]
38
+ #
31
39
  # # Whether to run the local development/test server or not
32
40
  # start_server: !Rails.env.production?,
33
41
  #
@@ -58,6 +66,9 @@ StaticRails.config do |config|
58
66
  #
59
67
  # # The destination of production-compiled assets, relative to Rails root
60
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"
61
72
  # },
62
73
  ]
63
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,25 @@
1
+ module StaticRails
2
+ class GetsCsrfToken
3
+ def call(req)
4
+ masked_authenticity_token(req.session)
5
+ end
6
+
7
+ private
8
+
9
+ def masked_authenticity_token(session, form_options: {})
10
+ ActionController::RequestForgeryProtection.instance_method(:masked_authenticity_token).bind(self).call(session, form_options)
11
+ end
12
+
13
+ def real_csrf_token(session)
14
+ ActionController::RequestForgeryProtection.instance_method(:real_csrf_token).bind(self).call(session)
15
+ end
16
+
17
+ def xor_byte_strings(s1, s2)
18
+ ActionController::RequestForgeryProtection.instance_method(:xor_byte_strings).bind(self).call(s1, s2)
19
+ end
20
+
21
+ def per_form_csrf_tokens
22
+ false
23
+ end
24
+ end
25
+ end
@@ -2,7 +2,7 @@ module StaticRails
2
2
  class MatchesRequestToStaticSite
3
3
  def call(request)
4
4
  StaticRails.config.sites.find { |site|
5
- subdomain_match?(site, request) && path_match?(site, request)
5
+ subdomain_match?(site, request) && path_match?(site, request) && !skip_path?(site, request)
6
6
  }
7
7
  end
8
8
 
@@ -20,5 +20,11 @@ module StaticRails
20
20
  def path_match?(site, request)
21
21
  request.path_info.start_with?(site.url_root_path)
22
22
  end
23
+
24
+ def skip_path?(site, request)
25
+ site.url_skip_paths_starting_with.any? { |path_start|
26
+ request.path_info.start_with?(path_start)
27
+ }
28
+ end
23
29
  end
24
30
  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|
@@ -3,6 +3,7 @@ module StaticRails
3
3
  :name,
4
4
  :url_subdomain,
5
5
  :url_root_path,
6
+ :url_skip_paths_starting_with,
6
7
  :source_dir,
7
8
  :start_server,
8
9
  :ping_server,
@@ -13,11 +14,13 @@ module StaticRails
13
14
  :server_path,
14
15
  :compile_command,
15
16
  :compile_dir,
17
+ :compile_404_file_path,
16
18
  keyword_init: true
17
19
  )
18
20
 
19
21
  def initialize(
20
22
  url_root_path: "/",
23
+ url_skip_paths_starting_with: [],
21
24
  start_server: !Rails.env.production?,
22
25
  ping_server: true,
23
26
  env: {},
@@ -25,11 +28,13 @@ module StaticRails
25
28
  server_path: "/",
26
29
  **other_kwargs
27
30
  )
31
+ @url_root_path = url_root_path
32
+ @url_skip_paths_starting_with = url_skip_paths_starting_with
28
33
  @start_server = start_server
34
+ @ping_server = ping_server
29
35
  @env = env
30
36
  @server_host = server_host
31
37
  @server_path = server_path
32
- @ping_server = ping_server
33
38
  super
34
39
  end
35
40
  end
@@ -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" => env["PATH_INFO"] + PATH_INFO_OBFUSCATION))
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 @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,26 @@
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
+ :valid_authenticity_token?,
11
+ :unmask_token,
12
+ :compare_with_real_token,
13
+ :valid_per_form_csrf_token?,
14
+ :xor_byte_strings,
15
+ :real_csrf_token
16
+ ].each do |method|
17
+ define_method method do |*args, **kwargs, &blk|
18
+ ActionController::RequestForgeryProtection.instance_method(method).bind(self).call(*args, **kwargs, &blk)
19
+ end
20
+ end
21
+
22
+ def per_form_csrf_tokens
23
+ false
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module StaticRails
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.7"
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.2
4
+ version: 0.0.7
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-06 00:00:00.000000000 Z
11
+ date: 2020-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -49,6 +49,7 @@ files:
49
49
  - ".gitignore"
50
50
  - ".standard.yml"
51
51
  - ".travis.yml"
52
+ - CHANGELOG.md
52
53
  - Gemfile
53
54
  - Gemfile.lock
54
55
  - LICENSE.txt
@@ -61,7 +62,9 @@ files:
61
62
  - lib/static-rails.rb
62
63
  - lib/static-rails/compile.rb
63
64
  - lib/static-rails/configuration.rb
65
+ - lib/static-rails/determines_whether_to_handle_request.rb
64
66
  - lib/static-rails/error.rb
67
+ - lib/static-rails/gets_csrf_token.rb
65
68
  - lib/static-rails/matches_request_to_static_site.rb
66
69
  - lib/static-rails/proxy_middleware.rb
67
70
  - lib/static-rails/rack_server_check.rb
@@ -69,7 +72,10 @@ files:
69
72
  - lib/static-rails/server.rb
70
73
  - lib/static-rails/server_store.rb
71
74
  - lib/static-rails/site.rb
75
+ - lib/static-rails/site_middleware.rb
76
+ - lib/static-rails/site_plus_csrf_middleware.rb
72
77
  - lib/static-rails/static_middleware.rb
78
+ - lib/static-rails/validates_csrf_token.rb
73
79
  - lib/static-rails/version.rb
74
80
  - lib/static-rails/waits_for_connection.rb
75
81
  - lib/tasks/static-rails.rake