Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle assets in dashboard when rails app is behind proxy path #424

Closed
lauer opened this issue Oct 13, 2021 · 10 comments
Closed

Handle assets in dashboard when rails app is behind proxy path #424

lauer opened this issue Oct 13, 2021 · 10 comments

Comments

@lauer
Copy link
Contributor

lauer commented Oct 13, 2021

The assets seem to always get fetched from /good_job/ (mount path)

But I have a rails app that is behind a proxy - users url path is /api/good_job/
Therefore the files can not be loaded.

Would it be possible so the dashboard load the assets files as e.g. ./bootstrap.css

@bensheldon
Copy link
Owner

bensheldon commented Oct 14, 2021

@lauer I don't have a lot of experience with configuring a Rails app for a proxy path. Is this descriptive of the configuration that you're using (e.g. config.relative_url_root)? https://timlentse.github.io/2015/12/06/How-to-mount-a-rails-app-in-a-subdirectory-with-NGINX.html

It looks like this might be a Rails bug 😞 : rails/rails#31476

Edit: Maybe this is a workaround to pass it explicitly to the URL generators in the dashboard: rails/rails#40237

@sandstrom
Copy link

sandstrom commented Oct 21, 2021

Suggestion; inline all assets (a dashboard doesn't have to look awesome, just ok, so the accompanying assets should be fairly minimal to begin with) and load them on every page render. Skip all hassle around CORS, proxy servers, etc.

@bensheldon
Copy link
Owner

@sandstrom I agree 99%. The assets right now are served statically as if they are regular routes/controllers/actions/views; they do not go through any asset pipeline:

scope controller: :assets do
constraints(format: :css) do
get :bootstrap, action: :bootstrap_css
get :chartist, action: :chartist_css
get :style, action: :style_css
end
constraints(format: :js) do
get :bootstrap, action: :bootstrap_js
get :chartist, action: :chartist_js
end

<%= stylesheet_link_tag bootstrap_path(format: :css, v: GoodJob::VERSION) %>
<%= stylesheet_link_tag chartist_path(format: :css, v: GoodJob::VERSION) %>
<%= stylesheet_link_tag style_path(format: :css, v: GoodJob::VERSION) %>
<%= javascript_include_tag bootstrap_path(format: :js, v: GoodJob::VERSION) %>
<%= javascript_include_tag chartist_path(format: :js, v: GoodJob::VERSION) %>

def bootstrap_css
render file: GoodJob::Engine.root.join("app", "assets", "vendor", "bootstrap", "bootstrap.min.css")
end

From looking into this issue, I did make an additional assumption: I think all of the paths/links are incorrect/broken, not just the assets (because the assets are linked like any other route/path). It's just that the asset paths are most visibly broken.

@bensheldon
Copy link
Owner

@lauer I was able to find a workaround for fixing GoodJob's Dashboard routes when using config.relative_url_root or RAILS_RELATIVE_URL_ROOT=.

Try adding this to an initializer:

# config/initializers/good_job.rb

module GoodJobRelativeUrlRoot
  def url_options
    options = super
    script_name = [Rails.application.config.relative_url_root, options[:script_name]].join("")
    options.merge(script_name: script_name)
  end
end

ActiveSupport::Reloader.to_prepare do
  GoodJob::ApplicationController.prepend(GoodJobRelativeUrlRoot)
end

Would you be able to give that a try? Thank you 🙏🏻

@tomfast
Copy link

tomfast commented Jul 29, 2022

Thanks for this @bensheldon . This helped me solve an issue I had related to rails/rails#31476

@bensheldon
Copy link
Owner

@tomfast thank you for letting me know! That's fantastic 🎉

I'm also linking in rails/rails#42243. Maybe I'll be able to get a PR upstream in Rails too.

@bensheldon
Copy link
Owner

To write up my discoveries. There are 2 ways to set up a proxy path with nginx (which is how I'm simulating this).

(1) Nginx provides subpath (proxy_pass trailing slash)

In this configuration, Nginx does not pass the full path to the application, only the subpath when proxy pass is configured with a trailing slash:

# nginx.conf
location /api {
  proxy_pass http://127.0.0.1:3000/; # <-- with a trailing slash
}

When requesting http://example.com/api/good_job Rack and Rails only see /good_job.

In this circumstance, configuration is solely focused on having Route helpers generate the appropriate urls so that links work. That was my workaround above to override url_options. That workaround:

  • Fixes routes generated within Controllers/Views, but...
  • Does not fix paths generated with GoodJob::Engine.routes.url_helpers (though GoodJob doesn't have any of these).
  • Does not fix Pipelined Asset paths (GoodJob doesn't have any of these either).

BUT, this does not seem to be the method that Rails embraces. Judging by Rails Engine Mount Tests, Rails prefers the following...

(2) Nginx provides full path (proxy_path slashless)

In this configuration, Nginx passes the full path to the application, without the proxy_pass trailing slash`:

# nginx.conf
location /api {
  proxy_pass http://127.0.0.1:3000; # <-- without a trailing slash
}

When requesting http://example.com/api/good_job Rack sees the full path /api/good_job. This requires updating the config.ru to use map:

# config.ru
map '/api' do
  run Rails.application
end

...which results in Rack passing to Rails a request with the subpath and SCRIPT_NAME=/api. In this configuration, your application will need to set config.relative_url_root / ENV['RAILS_RELATIVE_URL_ROOT'] or otherwise your assets and Rails.application.routes.url_helpers will be broken.

In this configuration, GoodJob works perfectly fine with no other configuration or workarounds 👍🏻

BUT, GoodJob::Engine.routes.url_helpers are broken. THIS IS THE RAILS BUG. (but that's also fine, because GoodJob doesn't use those helpers, yet.)

I made a little test route to show what's happening:

Rails.application.config.relative_url_root: /api
ENV['RAILS_RELATIVE_URL_ROOT']: 
request.env['SCRIPT_NAME']: /api
Rails.application.routes.default_url_options: {}

--------------

root_url: http://localhost:3000/api/

root_path: /api/
path_to_stylesheet("application"): /api/assets/application-e0cf9d8fcb18bf7f909d8d91a5e78499f82ac29523d475bf3a9ab265d5e2b451.css
good_job.root_path: /api/good_job/
Rails.application.routes.url_helpers.root_path: /api/

THE BROKEN ONE --->
GoodJob::Engine.routes.url_helpers.root_path: /good_job/
All the details


Rails.application.config.relative_url_root: /api
ENV['RAILS_RELATIVE_URL_ROOT']: 
request.env['SCRIPT_NAME']: /api
Rails.application.routes.default_url_options: {}

--------------

root_url: http://localhost:3000/api/

root_path: /api/
path_to_stylesheet("application"): /api/assets/application-e0cf9d8fcb18bf7f909d8d91a5e78499f82ac29523d475bf3a9ab265d5e2b451.css
good_job.root_path: /api/good_job/
Rails.application.routes.url_helpers.root_path: /api/
GoodJob::Engine.routes.url_helpers.root_path: /good_job/

--------------

request.env: {
  "rack.version": [
    1,
    6
  ],
  "rack.errors": "#<IO:0x000000012a884408>",
  "rack.multithread": true,
  "rack.multiprocess": false,
  "rack.run_once": false,
  "rack.url_scheme": "http",
  "SCRIPT_NAME": "/api",
  "QUERY_STRING": "",
  "SERVER_PROTOCOL": "HTTP/1.1",
  "SERVER_SOFTWARE": "puma 5.6.4 Birdie's Version",
  "GATEWAY_INTERFACE": "CGI/1.2",
  "REQUEST_METHOD": "GET",
  "REQUEST_PATH": "/api/",
  "REQUEST_URI": "/api/",
  "HTTP_VERSION": "HTTP/1.1",
  "HTTP_HOST": "localhost:3000",
  "HTTP_CONNECTION": "keep-alive",
  "HTTP_CACHE_CONTROL": "max-age=0",
  "HTTP_SEC_CH_UA": "\".Not/A)Brand\";v=\"99\", \"Google Chrome\";v=\"103\", \"Chromium\";v=\"103\"",
  "HTTP_SEC_CH_UA_MOBILE": "?0",
  "HTTP_SEC_CH_UA_PLATFORM": "\"macOS\"",
  "HTTP_DNT": "1",
  "HTTP_UPGRADE_INSECURE_REQUESTS": "1",
  "HTTP_USER_AGENT": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
  "HTTP_ACCEPT": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
  "HTTP_SEC_FETCH_SITE": "same-origin",
  "HTTP_SEC_FETCH_MODE": "navigate",
  "HTTP_SEC_FETCH_USER": "?1",
  "HTTP_SEC_FETCH_DEST": "document",
  "HTTP_ACCEPT_ENCODING": "gzip, deflate, br",
  "HTTP_ACCEPT_LANGUAGE": "en-US,en;q=0.9",
  "HTTP_COOKIE": "_ga=GA1.1.1522962611.1641243989; intercom-id-vvyajm8b=64ded6c9-daac-441d-9571-3a5b2f7c384b; order_shirts=merchant; _dayoftheshirt_visitor=kJ7mL5MkNLY9%2BABhG%2BadCqUQyEdwDazXCxwc96hZdnrFwZgrEeZw4goFFyXht1uVGdhcT5YimFT6nK91oExAliPHBU5ZSKxJluK11UGrgLuF9nuL3RBLed7yPo6y%2FsSIj2hPDa%2BhINBZ0ZRHe%2FnQX2QUkvLEAHOHrC7rcnJAAUAvXlTEAQngac8W%2F5P12yr5IkAVJRdy6sgkM57XLramjoupCMrhgXk1ME1rTdp4kyVaWslJz8ZNKx20qiATDG%2BTJEdQ%2BYw%2BQKwVxzpIK98OrAax--PrN96kki3S8xZBez--Cf%2FL0ak0fRjQqBJvuRAjYA%3D%3D; _brompt_session=Lv%2FmfPCiFSEDRCEJ%2FUEQthpYZUpC8Q4xSUYfMRfmcmiD6krS92IyBk6jHOJ4gXr0xKjiYVGwZsQsdCIZ2Ee%2FTGAQglOevzEniE9jMYZKPTvyWv9QkbLCUq7xzvMUnmsdVH9LuAP7dIXMPG2mf5VBS%2BQzx%2FxeiVFCk3tTw%2BGg%2F7uPdpB6u92fPqvqmh6ptPV%2BNuaP29Io45JTkShpgPVLCO93FWmEJ5lKzHB8I4jGumGAIXsx1AoCMNIWUZlG3yyvABQtfxwO9NJppVAjR0OzyG4tuH58Q0fa2wQn3%2FCdZGIsRJ0c7sw3dlnxOVG0%2FCY5LzF2I5%2BwAYI0b3vr67gJEMDJ3Q6Ow7grF3O%2Ba9bsPhKVQ6GZRt33ave%2BfMK2cEfJdUmMqGdjNg%3D%3D--3DBlw2%2FraQytTAy%2B--AKBsWL8qy6HTiXrPa4hW9A%3D%3D; _test_app_session=TK4QeuoF%2FnPxRB6pMun7bTdLePBiRDA4veYVJ0FkgMLfwZibJYCfQxf6w3CFAUukIVF8ZAdjMQ9AjC%2B8az608mDf1X4E6YTKbJSW9cebjbYx3%2BiiOtKkpo3KQTeTMHbCFErKwF7wym8NTShF1KKr%2BRReTM0xPwM8Sfewhz4dDgm27BTEvBQNUXibdeX3Vy5OHXqIIWM0qwlmWkl77gH8hmnAzAgeZ5%2FLz0VZiHc3NXWYLLOt42QQhxXbr4Yz7M3gwyidZJFtsBqDxq7xmOfrSzLwPXUX6do%2BPA%3D%3D--om7HVpFxw6FIvqW3--OYgJSspUDcITi7wz2grRpw%3D%3D; _good_job_demo_session=LpsoZJAKmgbrpQYko%2BdYg%2FIvWv98RVF%2FZyvQaK1wgvlcTklLgbzFf7dUnph%2BoEk5II0cFw5va%2BNOjxyvHkKolkCc7bWkk129OE%2BJlki0%2BeN2RJYphq46I1LfBnTpdEvIAjkdIje%2BBK9OwCUx4W9cuigIat0FbPjwVo3FmTqkIX%2F5Ulk%2Fc6ixrS0IQFR4sO4M5N0wXnF1wG7Syw7lbGsK2W%2Ft8P2eYtg3wC0VFWQ3BNMJqpMPFMVtkJfIpjICDNhohniRC4OC15wU%2BH2583Lba93iSB5NFr8ESc5LJyYC--o%2FSpNBV5jzzvnhJQ--U3lg2%2Fsia6EmMmSFyXepvQ%3D%3D",
  "HTTP_SEC_GPC": "1",
  "HTTP_IF_NONE_MATCH": "W/\"57bbc1f1335cc02b794cbffdd38baa60\"",
  "puma.request_body_wait": 0,
  "SERVER_NAME": "localhost",
  "SERVER_PORT": "3000",
  "PATH_INFO": "/",
  "REMOTE_ADDR": "::1",
  "puma.socket": "#<TCPSocket:0x000000011d9af7b8>",
  "rack.hijack?": true,
  "rack.hijack": "#<Puma::Client:0x000000011d9af790>",
  "rack.input": "#<Puma::NullIO:0x000000011f074f98>",
  "rack.after_reply": [

  ],
  "puma.config": "#<Puma::Configuration:0x000000011d89a670>",
  "action_dispatch.parameter_filter": [
    "passw",
    "secret",
    "token",
    "_key",
    "crypt",
    "salt",
    "certificate",
    "otp",
    "ssn"
  ],
  "action_dispatch.redirect_filter": [

  ],
  "action_dispatch.secret_key_base": "bc18884f26fe9140dc952f652357ce221a6e32be9261a4f07dd23da3dd6d1f409dde42ed3fd90d96fac40fdf12f58aa11408be30e8ec68ba0a61ce6627fc5e65",
  "action_dispatch.show_exceptions": true,
  "action_dispatch.show_detailed_exceptions": true,
  "action_dispatch.log_rescued_responses": true,
  "action_dispatch.logger": "#<ActiveSupport::Logger:0x000000011d0776a0>",
  "action_dispatch.backtrace_cleaner": "#<Rails::BacktraceCleaner:0x000000011c0ea728>",
  "action_dispatch.key_generator": "#<ActiveSupport::CachingKeyGenerator:0x000000012a737320>",
  "action_dispatch.http_auth_salt": "http authentication",
  "action_dispatch.signed_cookie_salt": "signed cookie",
  "action_dispatch.encrypted_cookie_salt": "encrypted cookie",
  "action_dispatch.encrypted_signed_cookie_salt": "signed encrypted cookie",
  "action_dispatch.authenticated_encrypted_cookie_salt": "authenticated encrypted cookie",
  "action_dispatch.use_authenticated_cookie_encryption": true,
  "action_dispatch.encrypted_cookie_cipher": null,
  "action_dispatch.signed_cookie_digest": null,
  "action_dispatch.cookies_serializer": "json",
  "action_dispatch.cookies_digest": null,
  "action_dispatch.cookies_rotations": "#<ActiveSupport::Messages::RotationConfiguration:0x000000011a80f668>",
  "action_dispatch.cookies_same_site_protection": "#<Proc:0x000000011c0e9c88 /Users/bensheldon/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/railties-7.0.3.1/lib/rails/application.rb:614>",
  "action_dispatch.use_cookies_with_metadata": true,
  "action_dispatch.content_security_policy": null,
  "action_dispatch.content_security_policy_report_only": false,
  "action_dispatch.content_security_policy_nonce_generator": null,
  "action_dispatch.content_security_policy_nonce_directives": null,
  "action_dispatch.permissions_policy": null,
  "action_dispatch.routes": "#<ActionDispatch::Routing::RouteSet:0x000000011d8c6608>",
  "ROUTES_11900_SCRIPT_NAME": "/api",
  "ORIGINAL_FULLPATH": "/api/",
  "ORIGINAL_SCRIPT_NAME": "/api",
  "action_dispatch.authorized_host": "localhost",
  "action_dispatch.request_id": "beb96057-dcdb-406a-aa4c-6006be7b6a38",
  "action_dispatch.remote_ip": "::1",
  "rack.session": "#<ActionDispatch::Request::Session:0x000000011a953858>",
  "rack.session.options": "#<ActionDispatch::Request::Session::Options:0x000000011a9537e0>",
  "rack.tempfiles": [

  ],
  "action_dispatch.request.path_parameters": {
    "controller": "roots",
    "action": "index"
  },
  "action_controller.instance": "#<RootsController:0x000000011a951378>",
  "action_dispatch.request.content_type": null,
  "action_dispatch.request.request_parameters": {
  },
  "rack.request.query_string": "",
  "rack.request.query_hash": {
  },
  "action_dispatch.request.query_parameters": {
  },
  "action_dispatch.request.parameters": {
    "controller": "roots",
    "action": "index"
  },
  "action_dispatch.request.formats": [
    "text/html"
  ]
}

--------------
Rails.application.config.action_controller: {
  "raise_on_open_redirects": true,
  "log_query_tags_around_actions": true,
  "wrap_parameters_by_default": true,
  "per_form_csrf_tokens": true,
  "forgery_protection_origin_check": true,
  "default_protect_from_forgery": true,
  "perform_caching": false,
  "assets_dir": "/Users/bensheldon/Repositories/bensheldon/good_job_demo/public",
  "logger": "#<ActiveSupport::Logger:0x000000011d0776a0>",
  "cache_store": "#<ActiveSupport::Cache::NullStore:0x000000011ba8dc40>",
  "javascripts_dir": "/Users/bensheldon/Repositories/bensheldon/good_job_demo/public/javascripts",
  "stylesheets_dir": "/Users/bensheldon/Repositories/bensheldon/good_job_demo/public/stylesheets",
  "asset_host": null,
  "relative_url_root": "/api"
}


# index.html.erb
<pre>

Rails.application.config.relative_url_root: <%= Rails.application.config.relative_url_root %>
ENV['RAILS_RELATIVE_URL_ROOT']: <%= ENV['RAILS_RELATIVE_URL_ROOT'] %>
request.env['SCRIPT_NAME']: <%= request.env['SCRIPT_NAME'] %>
Rails.application.routes.default_url_options: <%= Rails.application.routes.default_url_options %>

--------------

root_url: <%= root_url %>

root_path: <%= root_path %>
path_to_stylesheet("application"): <%= path_to_stylesheet "application" %>
good_job.root_path: <%= good_job.root_path %>
Rails.application.routes.url_helpers.root_path: <%= Rails.application.routes.url_helpers.root_path %>
GoodJob::Engine.routes.url_helpers.root_path: <%= GoodJob::Engine.routes.url_helpers.root_path %>

--------------

request.env: <%= JSON.pretty_generate request.env %>

--------------
Rails.application.config.action_controller: <%= JSON.pretty_generate Rails.application.config.action_controller %>

</pre>

I think this can be fixed, or at least have a simple failing test case in this engine test section by show that Bukkit::Engine.routes.url_helpers.root_path equals the expected value but doesn't. Then we can fix it!

@bensheldon
Copy link
Owner

I've made a Rails PR: rails/rails#45719

@bensheldon
Copy link
Owner

The fix in rails/rails#45719 has been merged. I don't believe it will be backported, but I think there are enough workarounds in this thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

No branches or pull requests

4 participants