I have an app I created on Heroku which is written in Ruby (not rails) and Sinatra.
It is hosted on the default herokuapp domain so I can address the app with both HTTP and HTTPS.
The app requests user credentials which I forward on to an HTTPS call so the forwarding part is secure.
I want to ensure my users always connect securely to my app so the credentials aren't passed in clear text.
Despite lots of research, I've not found a solution to this simple requirement.
Is there a simple solution without changing my app to Ruby rails or otherwise?
Thanks,
Alan
I use a helper that looks like this:
def https_required!
if settings.production? && request.scheme == 'http'
headers['Location'] = request.url.sub('http', 'https')
halt 301, "https required\n"
end
end
I can then add it to any single route I want to force to https, or use it in the before filter to force on a set of urls:
before "/admin/*" do
https_required!
end
Redirect in a Before Filter
This is untested, but it should work. If not, or if it needs additional refinement, it should at least give you a reasonable starting point.
before do
redirect request.url.sub('http', 'https') unless request.secure?
end
See Also
Filters
Request Object
RackSsl::Enforcer
Related
I am trying to learn how to develop a simple web app using pure Ruby. I have a simple backend service which I created as a Sinatra app. While developing it, I noticed the frontend (a simple HTML/JS static site) would not communicate with it because of CORS policies. So I looked into how to pass headers from Sinatra.
I came across sinatra-cors. I set it up as instructed and my app looks like this (abbreviated):
require 'sinatra'
require 'sinatra/cors'
set :allow_origin, '*'
set :allow_methods, 'GET,HEAD,POST'
set :allow_headers, 'content-type,if-modified-since,access-control-allow-methods,access-control-allow-origin'
set :expose_headers, 'content-disposition'
set :allow_credentials, true
post '/' do
[...]
end
When I run it with ruby app.rb, it works perfectly. Frontend can communicate and CORS policies are observed.
Now, I want to set up the service for a production environment. For that, I want to use Puma. So with Puma, I have a config.ru which looks like this:
require File.expand_path('app', File.dirname(__FILE__))
run WebApp
and I modified my app.rb to look like this (again abbreviated):
require 'sinatra'
require 'sinatra/cors'
class WebApp < Sinatra::Application
set :allow_origin, '*'
set :allow_methods, 'GET,HEAD,POST'
set :allow_headers, 'content-type,if-modified-since,access-control-allow-methods,access-control-allow-origin'
set :expose_headers, 'content-disposition'
set :allow_credentials, true
post '/' do
[...]
end
end
basically, wrapped the app in a class, and call it from the config.ru. When I run this by running puma in the directory, the service comes up, but headers are no longer passed back. Whenever I try to hit the backend, I get:
Access to XMLHttpRequest at 'http://localhost:4567/' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
This was the error I was getting before I originally set up the headers. So it seems pretty clear to me that the set parameters are being ignored.
So, this seems like a simple matter, but I have not been able to find a proper answer: How do I make Puma respect the 'set' parameters? Or alternatively, how do I achieve the same desired result?
It seems clear to me that I am missing a very simple thing, but I cannot figure out what exactly it is.
Thanks in advance!
It looks like you are just missing register Sinatra::Cors in your class.
class WebApp < Sinatra::Application
register Sinatra::Cors # Add this line.
set :allow_origin, '*'
# etc.
I Heroku, is there any way to get my heroapp domain to point to my custom domain? I have APPNAME.herokuapp.com which was setup by heroku and I got www.appname.com. www.appname.com works. But I want APPNAME.herokuapp.com to redirect to www.appname.com.
In short:
appname.herokuapp.com redirect -> www.appname.com
Sure. You can detect the Host header in your code and redirect based on that.
In ruby for example:
if ENV["HTTP_HOST"] == "myapp.herokuapp.com"
// Send the redirection and finish the request
end
I implemented a small jruby sinatra app and if i run it directly on
WEBrick locally all the routings work perfectly. However when I deploy
the war file (i use warbler) to a server instance (like
"example.com/myapp" or "localhost:8080/myapp") I have routing
issues within the post requests.
For example:
get '/login' do
slim :login
end
post '/login' do
session.clear
login_correct? = check_password (params[:user], params[:pass])
if(login_correct?)
session[:user] = params[:user]
redirect to('/')
else
redirect to('/login')
end
end
get '/redirect' do
redirect to('/login')
end
Here the 3rd route handler (get '/redirect' do ..) redirects to
localhost:8080/myapp/login properly with status code 303, however 2nd route handler redirects
to localhost:8080/login with status code 404.
What should i do so that redirections in post route handler works
properly when i deploy the app?
Thanks a lot!
UPDATE on Solution: After checking the code again and again I realized that the problem was me using form action = '/login' in slim:login instead of form action= "#{url('/login')}". So it wasn't even handled by the post route handler since the post request was sent to localhost:8080/login but I thought it was route handler who is redirecting it to there..
try setting set :prefixed_redirects, true with Sinatra
(it should than use rack.env['SCRIPT_NAME'] with redirects)
UPDATE: even without the set :prefixed_redirects, true (default is false) works perfectly fine under Trinidad (which should behave the same as Warbler since bot use JRuby-Rack) ... request.env['SCRIPT_NAME'] is set to the /context_path correctly.
I would try updating (if they're not the latest) JRuby-Rack/Warbler as well as Sinatra as a last resort, otherwise this probably needs a detailed look at what's going on (esp. if SCRIPT_NAME is set correctly).
In Camping/Rack, how can I get the base URL for my app? I want to know so I can put it in an email it sends.
It might be (in development)
or
http://localhost:9292
or
http://localhost:80/game
or in production
http://fancy-snake.heroku.com
So far I have
url = #env['rack.url_scheme'] + "://" + #env['HTTP_HOST'] + R(LoginX, u.secret)
Which seems to work for the first and third cases. I don't know if it's write if the app is at localhost/prefix
You have to be a little careful with this, as there are a some subtle potential traps. The Rack::Request class will probably be helpful here.
First, you can’t really get the url for the app, as it may be responding to multiple urls (via Rack routes, Apache config, etc), so you’re looking at getting the url for the particular request. If you’re only serving requests from one url this won’t matter.
The scheme for the request is in the env hash under the rack.url_scheme, but this is only for the “last leg” of the request. If your app is behind a proxy of some sort (Nqinx, Apache etc.) then you want to get the scheme of the real request, not the request from the proxy to the machine your app is running on. If you’ve configured your proxy correctly it should be setting a header so you can tell what the original scheme was. Rack::Request has a scheme method that takes these headers into account.
The host for the url is probably in the env hash under the HTTP_HOST key, but this header is not necessarily present (admittedly that’s pretty unlikey nowadays). You should fall back on SERVER_NAME and SERVER_PORT. Additionally there’s the issue of handling proxied requests, you want the hostname of the original request, not the backend server. Again, Rack::Request provides host_with_port and host methods that deal with these issues.
Rack::Request also provides a base_url method that combines scheme and host, and additionally only includes the port if differs from the default (80 or 443).
The location that your app is mounted is in the env hash under the SCRIPT_NAME key. This would be /game in your second example, and can be empty if your app mounted at the root of your server. Again, Rack::Request provides a script_name method, although this one simply returns the value of the entry in the env hash.
So, in summary, you probably want to use something like this:
req = Rack::Request.new env
url = req.base_url + req.script_name
which looks pretty simple, but is taking care of various possibilities for you.
Additionally, you miight find the the Rack specification useful to have a read of, it contains details of the various entries that should be in the env hash.
Camping has a helper called URL which returns the absolute URL to your app:
URL() # => #<URL:http://test.ing/blog/>
URL() + "view/12" # => #<URL:http://test.ing/blog/view/12>
URL("/view/12") # => #<URL:http://test.ing/blog/view/12>
I am working on a website hosted on microsoft's office live service. It has a contact form enabling visitors to get in touch with the owner. I want to write a Ruby script that sits on a seperate sever and which the form will POST to. It will parse the form data and email the details to a preset address. The script should then redirect the browser to a confirmation page.
I have an ubuntu hardy machine running nginx and postfix. Ruby is installed and we shall see about using Thin and it's Rack functionality to handle the script. Now it's come to writing the script and i've drawn a blank.
It's been a long time and if i remember rightly the process is something like;
read HTTP header
parse parameters
send email
send redirect header
Broadly speaking, the question has been answered. Figuring out how to use the answer was more complicated than expected and I thought worth sharing.
First Steps:
I learnt rather abruptly that nginx doesn't directly support cgi scripts. You have to use some other process to run the script and get nginx to proxy requests over. If I was doing this in php (which in hind sight i think would have been a more natural choice) i could use something like php-fcgi and expect life would be pretty straight forward.
Ruby and fcgi felt pretty daunting. But if we are abandoning the ideal of loading these things at runtime then Rack is probably the most straight forward solution and Thin includes all we need. Learning how to make basic little apps with them has been profoundly beneficial to a relative Rails newcomer like me. The foundations of a Rails app can seem hidden for a long time and Rack has helped me lift the curtain that little bit further.
Nonetheless, following Yehuda's advice and looking up sinatra has been another surprise. I now have a basic sinatra app running in a Thin instance. It communicates with nginx over a unix socket in what i gather is the standard way. Sinatra enables a really elegant way to handle different requests and routes into the app. All you need is a get '/' {} to start handling requests to the virtual host. To add more (in a clean fashion) we just include a routes/script.rb into the main file.
# cgi-bin.rb
# main file loaded as a sinatra app
require 'sinatra'
# load cgi routes
require 'routes/default'
require 'routes/contact'
# 404 behaviour
not_found do
"Sorry, this CGI host does not recognize that request."
end
These route files will call on functionality stored in a separate library of classes:
# routes/contact.rb
# contact controller
require 'lib/contact/contactTarget'
require 'lib/contact/contactPost'
post '/contact/:target/?' do |target|
# the target for the message is taken from the URL
msg = ContactPost.new(request, target)
redirect msg.action, 302
end
The sheer horror of figuring out such a simple thing will stay with me for a while. I was expecting to calmly let nginx know that .rb files were to be executed and to just get on with it. Now that this little sinatra app is up and running, I'll be able to dive straight in if I want to add extra functionality in the future.
Implementation:
The ContactPost class handles the messaging aspect. All it needs to know are the parameters in the request and the target for the email. ContactPost::action kicks everything off and returns an address for the controller to redirect to.
There is a separate ContactTarget class that does some authentication to make sure the specified target accepts messages from the URL given in request.referrer. This is handled in ContactTarget::accept? as we can guess from the ContactPost::action method;
# lib/contact/contactPost.rb
class ContactPost
# ...
def action
return failed unless #target.accept? #request.referer
if send?
successful
else
failed
end
end
# ...
end
ContactPost::successful and ContactPost::failed each return a redirect address by combining paths supplied with the HTML form with the request.referer URI. All the behaviour is thus specified in the HTML form. Future websites that use this script just need to be listed in the user's own ~/cgi/contact.conf and they'll be away. This is because ContactTarget looks in /home/:target/cgi/contact.conf for the details. Maybe oneday this will be inappropriate, but for now it's just fine for my purposes.
The send method is simple enough, it creates an instance of a simple Email class and ships it out. The Email class is pretty much based on the standard usage example given in the Ruby net/smtp documentation;
# lib/email/email.rb
require 'net/smtp'
class Email
def initialize(from_alias, to, reply, subject, body)
#from_alias = from_alias
#from = "cgi_user#host.domain.com"
#to = to
#reply = reply
#subject = subject
#body = body
end
def send
Net::SMTP.start('localhost', 25) do |smtp|
smtp.send_message to_s, #from, #to
end
end
def to_s
<<END_OF_MESSAGE
From: #{#from_alias}
To: #{#to}
Reply-To: #{#from_alias}
Subject: #{#subject}
Date: #{DateTime::now().to_s}
#{#body}
END_OF_MESSAGE
end
end
All I need to do is rack up the application, let nginx know which socket to talk to and we're away.
Thank you everyone for your helpful pointers in the right direction! Long live sinatra!
It's all in the Net module, here's an example:
#net = Net::HTTP.new 'http://www.foo.com', 80
#params = {:name => 'doris', :email => 'doris#foo.com'}
# Create HTTP request
req = Net::HTTP::Post.new( 'script.cgi', {} )
req.set_form_data #params
# Send request
response = #net.start do |http|
http.read_timeout = 5600
http.request req
end
Probably the best way to do this would be to use an existing Ruby library like Sinatra:
require "rubygems"
require "sinatra"
get "/myurl" do
# params hash available here
# send email
end
You'll probably want to use MailFactory to send the actual email, but you definitely don't need to be mucking about with headers or parsing parameters.
CGI class of Ruby can be used for writing CGI scripts. Please check: http://www.ruby-doc.org/stdlib/libdoc/cgi/rdoc/index.html
By the way, there is no need to read the HTTP header. Parsing parametres will be easy using CGI class. Then, send the e-mail and redirect.