static-rails 0.0.2 → 0.0.7

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: 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