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 +4 -4
- data/CHANGELOG.md +35 -0
- data/Gemfile.lock +11 -11
- data/README.md +115 -15
- data/lib/generators/templates/static.rb +11 -0
- data/lib/static-rails/configuration.rb +6 -2
- data/lib/static-rails/determines_whether_to_handle_request.rb +15 -0
- data/lib/static-rails/gets_csrf_token.rb +25 -0
- data/lib/static-rails/matches_request_to_static_site.rb +7 -1
- data/lib/static-rails/proxy_middleware.rb +3 -4
- data/lib/static-rails/railtie.rb +5 -10
- data/lib/static-rails/site.rb +6 -1
- data/lib/static-rails/site_middleware.rb +58 -0
- data/lib/static-rails/site_plus_csrf_middleware.rb +50 -0
- data/lib/static-rails/static_middleware.rb +10 -1
- data/lib/static-rails/validates_csrf_token.rb +26 -0
- data/lib/static-rails/version.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 71c1a0e6ce72ea1feb691e3cbfae0bec8bf723f1512cda383aeae57d56fd9765
|
4
|
+
data.tar.gz: 4d8d3c148661b2498ccfeb3677553e94a91c2cb68b5c48ba47aaace89be8adfe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc39f818e745f67930b8821dbf3b38cdfd34d33accd25979801a4fbc34df67cdf600e517a2fe806f6094e43bc2e797fcef2aa4b1bb7006f3d75317a26b19ea82
|
7
|
+
data.tar.gz: c0879dc6ef5950806aaa662035ac7593a78c334dc578f65bd5e13526d1f4c770a08b9cf1f2b8fc8e21a9d00372876736924e05687e4d33ebbb780acc5b802ea1
|
data/CHANGELOG.md
ADDED
@@ -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
|
data/Gemfile.lock
CHANGED
@@ -1,32 +1,32 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
static-rails (0.0.
|
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.
|
12
|
-
actionview (= 6.0.
|
13
|
-
activesupport (= 6.0.
|
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.
|
19
|
-
activesupport (= 6.0.
|
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.
|
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.
|
60
|
-
actionpack (= 6.0.
|
61
|
-
activesupport (= 6.0.
|
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
|
[](https://circleci.com/gh/testdouble/static-rails)
|
4
4
|
|
5
|
-
##
|
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
|
29
|
-
|
30
|
-
|
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
|
33
|
-
|
34
|
-
|
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
|
37
|
-
|
38
|
-
|
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](
|
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,
|
16
|
+
# When true, our middleware will proxy requests to static site servers
|
17
17
|
attr_accessor :proxy_requests
|
18
18
|
|
19
|
-
# When true,
|
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
|
-
|
31
|
+
|
33
32
|
super(env)
|
34
33
|
else
|
35
34
|
@app.call(env)
|
data/lib/static-rails/railtie.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require_relative "rack_server_check"
|
2
2
|
require_relative "server_store"
|
3
|
-
require_relative "
|
4
|
-
require_relative "
|
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
|
-
|
13
|
-
|
14
|
-
|
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|
|
data/lib/static-rails/site.rb
CHANGED
@@ -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
|
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
|
data/lib/static-rails/version.rb
CHANGED
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
|
+
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-
|
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
|