Rack Rewrite before force_ssl Rails > 3.2 Heroku - ruby-on-rails

I'm using Rack Rewrite to 301 redirect my apex/root domain to my www domain because my wildcard SSL doesn't support the root domain. I'd also like to force SSL sitewide but can't seem to get my rewrite to occur before the force SSL. I've tried a few things, namely the response in this answer: https://stackoverflow.com/a/8217170/535632
Here's my rewrite code:
Gospot::Application.config.middleware.insert_before(Rack::Runtime, Rack::Rewrite) do
if Rails.env.production?
r301 %r{.*}, Proc.new {|path, rack_env| "http://www.#{rack_env['SERVER_NAME']}#{path}" },
:if => Proc.new {|rack_env| rack_env['SERVER_NAME'] == 'mydomain.com'}
end
end
I've tried:
require 'rack/ssl'
Gospot::Application.config.middleware.insert_before(Rack::SSL, Rack::Rewrite) do
Instead of using config.force_ssl = true in production.rb but I get the following error on Heroku:
No such middleware to insert before: Rack::SSL (RuntimeError)
Is there anyway to run my rack rewrite before the force_ssl? I've found a lot of answers but they all seem to work for Rails < 3.1

I ran into the same issue where I wanted to use rack-rewrite to redirect based on the domain before forcing SSL. I was able to accomplish this in Rails 4 by inserting the Rack::Rewrite middleware before ActionDispatch::SSL as shown in the code below.
config.middleware.insert_before(ActionDispatch::SSL, Rack::Rewrite) do
# redirects / rewrites here
end

The easiest way to do this, depending on your DNS provider, would make your apex DNS record a CNAME for your www subdomain.
If that won't work for you, then you should find a middleware that does exist in your stack to place this rewrite before. Try this:
heroku run rake middleware
To get an idea of what your middleware stack looks like. Choose a piece of middleware that's relatively high up the chain and place the rewrite before it. There's definitely some guess and check that will be going on here, probably, but this will eventually correct your problem.

I had the same problem (Rails 3). Apparently force_ssl adds Rack:SSL to the top of the middleware. To insert before it you have to add it as a string or else you will get an "uninitialized constant" error.
Here is the code I ended up using:
SM::Application.config.middleware.insert_before("Rack::SSL", Rack::Rewrite) do
# rewrite code
end

Related

Upgraded Rails to 6, getting Blocked host Error

I needed the new function in ActiveStorage to resize_to_fill so I upgraded to Ruby 2.5.1 and Rails 6.
ruby '2.5.1'
gem "rails", github: "rails/rails"
When I stopped, then restarted my server (Cloud 9), I received the below Rails error:
Blocked host: xxxxxxx-xxxxxxx.c9users.io
To allow requests to xxxxxxx-xxxxxxx.c9users.io, add the following configuration:
Rails.application.config.hosts << "xxxxxxx-xxxxxxx.c9users.io"
I've tried restarting, new windows, but nothing worked. I've never seen this error before. I'm guessing the new version of Rails is doing something?
The Blocked Host is a new feature of Rails 6. You can add this pattern to your config/environments/development.rb to have no worries of that in case of dynamic urls
config.hosts << /[a-z0-9]+\.c9users\.io/
Also for ngrok user, just replace above c9users by ngrok
Update: ngrok is currently using - and . as subdomain in their URLs so this should be accurate config.hosts << /[a-z0-9-.]+\.ngrok\.io/
Source: https://github.com/MikeRogers0/puma-ngrok-tunnel
If you want to disable this functionality on your development environment, you can add config.hosts.clear to config/environments/development.rb.
Add this line to config/environments/development.rb
config.hosts << /.*\.ngrok\.io/
Restart your rails server and it will work
This article worked for me:
The first option is to whitelist the hostnames in config/environments/development.rb:
Rails.application.configure do
config.hosts << "hostname" # Whitelist one hostname
config.hosts << /application\.local\Z/ # Whitelist a test domain
end
The second option is to clear the entire whitelist, which lets through requests for all hostnames:
Rails.application.configure do
config.hosts.clear
end
Credit goes to Manfred Stienstra.
To allow requests from any subdomain of ngrok.io (or other service), the simplest solution is to prepend it with . like so:
# config/environments/development.rb
Rails.application.configure do
...
config.hosts << '.ngrok.io'
end
No need to use a regexp for subdomains like mentioned in some other answers.
PS: don't disable this functionality by doing config.hosts.clear as mentioned in some other answers, as this defeats the purpose of Rails' DNS rebinding protection, and under the right circumstances an outside attacker could gain full access to your local Rails app information (source).
In Rails 6 Action Pack introduced ActionDispatch::HostAuthorization and by default allows only [IPAddr.new(“0.0.0.0/0”), IPAddr.new(“::/0”), “localhost”]
You can add arrays of RegExp, Proc, IPAddr and String or a single String in the file config/application.rb like this
class Application < Rails::Application
config.hosts << "xxxxxxx-xxxxxxx.c9users.io"
...
end
From "https://drivy.engineering/rails-6-unnoticed-features":
Rails 6 added a new middleware called
ActionDispatch::HostAuthorization allowing you to whitelist some hosts
for your application and preventing Host header attacks. You can
easily configure it with a String, IPAddr, Proc and RegExp (useful
when dealing with wildcard domains).
I added Rails.application.config.hosts << "xxxxxxx-xxxxxxx.c9users.io" to config/application.rb and it fixed my test app fine. Then I did it to my real app and it also worked. The problem is, Devise threw an error as well, which apparently won't be fixed until at least Rails 6 beta. I guess I'm going back to Carrierwave for my image sizing needs until ActiveStorage is more mature.
In Rails 6, when you want to allow host from ngrok v2.3.40, add this config into config/environments/development.rb
config.hosts << /[a-z0-9\-]+\.ap\.ngrok\.io/
Restart server and enjoy
Add this line to config/environments/development.rb
config.hosts << /.+\.ngrok\.io:\d+/
Most of the responses I see are missing the port part of the URL. If you are accessing this URL in a specific port (typically :3000) the :\d+ part of the regular expression is necessary.
It will work after restarting your server.
config.hosts = nil
Use this in development.rb and and restart your rails server, it works for me, it will work.
HEADS UP : You may whitelist your host with the config application.config.hosts << 'your_unvalid_host_name' but still have the error.
The error message is currently not accurate in this case. See this issue.
You should not use hostname with underscore.
NB: The application.config.hosts.clear is working in this case.
In order to support hyphens in the ngrok subdomain name and region, you need to change config/environments/development.rb change config.hosts to /[a-z0-9.-]+.ngrok.io/
Example:
config.hosts = (config.hosts rescue []) << /[a-z0-9.-]+.ngrok.io/
1st run the ngrok 3000 in one of the terminals and next open the new terminal and run rails s... then u can see now ngrok and rails s both can run simultaneously...

Request origin not allowed: http://localhost:3001 when using Rails5 and ActionCable

Having server issues with an app in Rails 5.0.0.beta2 trying to use ActionCable.
Using localhost:3000 works fine, as that is what most of ActionCable defaults to. But if I try to run the rails server on port 3001, it gives me Request origin not allowed: http://localhost:3001
The ActionCable docs mention using something like ActionCable.server.config.allowed_request_origins = ['http://localhost:3001'] which does work for me if I put it in config.ru
But that seems like a really weird place to put it. I feel like it should be able to go in an initializer file, or my development.rb environment config file.
To further prove my point that it should be allowed to go in there, the setting ActionCable.server.config.disable_request_forgery_protection = true works to ignore request origin, even when I include it in development.rb.
Why would ActionCable.server.config.disable_request_forgery_protection work in development.rb, but ActionCable.server.config.allowed_request_origins doesn't (but does work in config.ru)?
Not a pressing issue, since I have several options as a work around. I just want to know if I'm missing something obvious about how I imagine this should be working.
You can put
Rails.application.config.action_cable.allowed_request_origins = ['http://localhost:3001'] in your development.rb
See https://github.com/rails/rails/tree/master/actioncable#allowed-request-origins for more informations
For my flutter app, request origin was nil. So, needed to add nil in the list.
I have added this code in config/environments/development.rb, and it works!
config.action_cable.allowed_request_origins = [/http:\/\/*/, /https:\/\/*/, /file:\/\/*/, 'file://', nil]
From this answer, you can also add the following code to config/environments/development.rb to allow requests from both http and https:
Rails.application.configure do
# ...
config.action_cable.allowed_request_origins = [%r{https?://\S+}]
end
config.action_cable.allowed_request_origins accepts an array of strings or regular expressions as the documentation states:
Action Cable will only accept requests from specified origins, which
are passed to the server config as an array. The origins can be
instances of strings or regular expressions, against which a check for
the match will be performed.
The regex listed below will match both http and https urls from any domain so be careful when using them. It is just a matter of preference which one to use.
[%r{https?://\S+}] # Taken from this answer
[%r{http[s]?://\S+}]
[%r{http://*}, %r{https://*}]
[/http:\/\/*/, /https:\/\/*/]

devise confirmation_url in HTTPS

My website is entirely SSL, so I would like to have also the urls generated by devise (3.2.2) for Email verification to be https://....
currently the urls are generated by:
confirmation_url(#resource, :confirmation_token => #token)
which produces nice urls like:
http://example.com/users/confirmation?confirmation_token=zqfHS35ckLQZscSbsgMm
I would like the url to be
https://example.com/users/confirmation?confirmation_token=zqfHS35ckLQZscSbsgMm
Also, currently the email verification doesn't work, because nginx operates a redirect to the https equivalent to every page, and for some reasons things get messed up and the https version is a corrupted url, like:
https://example.com/users/confirmation?confirmation_token=zqfHS35ckLQZscSbsgMm?confirmation_token=zqfHS35ckLQZscSbsgMm
for some reasons nginx redirects to this corrupted url, so Unicorn can't but reject the request.
any clues?
You can either specify the protocol in the email template, as you did in your own answer, or you can specify it as a default in the mailer. The simplest way to do this, if you are happy for all emails to use https links, is to add it to your app config. For example, in your production.rb:
config.action_mailer.default_url_options = {:protocol => 'https', :host => 'example.com'}
I know it doesnt matter any more if you're going straight to https, but your url after the nginx redirect from http to https looks like it's appending the query string to the entire url, so it would be worth fixing that so it works in all cases even if you don't need it for the emails any more. If you're using a return 301 … statement in the nginx config, perhaps there's a trailing $query_string or $args you don't need - for example, if you're using $request_uri that already has the GET parameters in it.
Also, I don't think you will find confirmation_url defined directly anywhere. If you try rake routes you'll probably see one of them is:
user_confirmation GET /users/confirmation(.:format) {:controller=>"devise/confirmations", :action=>"show"}
which means that there will automatically be a user_confirmation_url helper available as with routes in general. I think devise then allows you to use confirmation_url due to its clever tracking of the scope you're using ('user' in this case), though I must admit I haven't looked at the code enough in devise's routing to know exactly how it does it for the routes.
I changed the method call to:
confirmation_url(#resource, :confirmation_token => #token, protocol: "https")
and that started generating correctly the urls with https as required.
I couldn't find the definition of confirmation_url anywhere in the devise code though.

Heroku + Rack-Rewrite

Still can't get this working...Rails 3.1.3, Ruby 1.9.2 on Heroku's Cedar Stack.
Trying to use https://github.com/jtrupiano/rack-rewrite to make http://domain 301 redirect to http://www.domain to no luck (app works, but no redirects happen at all).
/config/initializers/rack_rewrite.rb (MyAppName is actually the correct name, domain.com is actual domain):
MyAppName::Application.config.middleware.insert_before(Rack::Lock, Rack::Rewrite) do
r301 %r{.*}, 'http://www.domain.com$&', :if => Proc.new {|rack_env|
rack_env['SERVER_NAME'] != 'www.domain.com'
}
end
Added to Gemfile:
gem 'rack-rewrite'
Did "gem install rack-rewrite", "bundle install".
No luck.
Any ideas?
UPDATE:
I have figured out PART of the problem. Since I'm just trying to serve "index.html" and it's "/style" folder, it appears that having "index.html" in "/public" overrides the rack-rewrite. If I remove "index.html", the rewrites work...but now I don't know where to put the files, or set up the routes.rb to direct to the index.html page by default...any help?
change
rack_env['SERVER_NAME'] != 'www.domain.com'
to
rack_env['SERVER_NAME'] == 'domain.com'
I think that maybe env["SERVER_NAME"] could be an internal dns in this case like 'app7009.intra.foo'. I do some stuff with domains in middleware in heroku: I look at both env['REQUEST_URI'] and env['PATH_INFO'], mainly because the POW-server I use locally doesn't set REQUEST_URI. It's a bit different how different servers populate the env hash, I wish this URL request part would be more standard with something like rack.
env['REQUEST_URI'] !~ /www.domain.com/
I'm new to this so I have no logical explanation as to why it works but it worked for me when I put the codes in config/application.rb instead of a new file /config/initializers/rack_rewrite.rb.

Support for multiple domains/subdomains in Rails

I have a Rails app that has a similar setup to Tumblr, that is, you can have either:
(1) Subdomain hosting (your-username.myapp.com)
(2) Domain hosting (your-username.com)
Both would forward to a personalized website for that user, created with my application.
How can I accomplish this in Rails? I have been able to get (1) working with subdomain-fu, but I'm not sure how to get (2) working. Any pointers (plugins, gems, tutorials), etc. would be greatly helpful, I can't seem to find any.
Thanks!
The principle for domains is the same as the subdomain - find the domain, map to an account.
The details will depend on how your hosting is going to handle the DNS.
I am currently using Heroku and its wildcard service.
In this case, the domain is mapped with a cname to the subdomain hosted by my Heroku app. From here I can work out the associated account and details.
EDIT: I've found a much easier way: http://www.arctickiwi.com/blog/7-host-and-domain-based-routing-in-ruby-on-rails
Not exactly an answer but this is the best I can give. Maybe this'll help you too.
Ideally, this blog post from transfs.com and subdomain-fu should do the trick. I've been trying to implement it, however, and they don't seem to play nicely together.
Basically, if I don't include the intiializer, the subdomain route works fine. If I include the initializer, the subdomain route breaks (everything gets caught by map.root). I have a feeling it's with the way it builds the condition string in the initializer. If you can figure out how it breaks, then you'll have a working app.
My initializer:
module ActionController
module Routing
class RouteSet
def extract_request_environment(request)
env = { :method => request.method }
env[:domain] = request.domain if request.domain
env[:host] = request.host if request.host
env
end
end
class Route
alias_method :old_recognition_conditions, :recognition_conditions
def recognition_conditions
result = old_recognition_conditions
[:host, :domain].each do |key|
if conditions[key]
operator = "==="
if conditions[key].is_a?(Regexp)
operator = "=~"
end
result << "conditions[:#{key.to_s}] #{operator} env[:#{key.to_s}]"
end
end
result
end
end# end class Route
end
end
My routes (just for development). You'll see my local development domain, stiltify.dev. Sorry, I tried to make it look good in here but I couldn't get the code block to look nice. I put it on pastie instead: http://pastie.org/940619.
The comments section in Ryan Bates' screencast was very helpful, and got me to figure out the subdomain => false and the other errors they were getting into. Still didn't fix the problem though!

Resources