How do I read cookies with Sinatra? - ruby

I've been having a lot of trouble getting cookies working with my web application running Sinatra.
I am currently setting the cookies with:
response.set_cookie(:id, :value => id, :domain => "XX.XXX.XXX.XXX", :expires => Time.now + 86400000)
where the domain is the IP address of the web app (no proper domain for now). This correctly sets the cookie because I can find the cookie in my web browser's cookies and the values are correct.
However, I can't read the cookie. If I write:
id = request.cookies[:id]
then id just becomes a null value.
Is there something I'm missing (for instance are there any settings I should be aware of)? How can I get this to work?
All help would be appreciated. Thanks in advance.

OK, I managed to figure it out. I wasn't setting the path so it wouldn't work across different URLs.
I found this fixed my problem:
response.set_cookie(:id, :value => id, :domain => "XX.XXX.XXX.XXX", :path => "/", :expires => Time.now + 86400000)

Related

How do I limit access to API endpoints in Rails 4?

I'm trying to figure out how to limit Cross-Domain access to a JSON API for 3 sites. It seems you have a few different things to deal with in Rails 4, such as:
protect_from_forgery with: :exception which prevents CSRF attacks.
Changing this to protect_from_forgery with: :null_session seems to allow the world to access it.
Trying this in application.rb:
config.action_dispatch.default_headers.merge!({
'Access-Control-Allow-Origin' => '*',
'Access-Control-Request-Method' => 'GET, POST, PUT'
})
This, again, allows the world to access your endpoints, however, it only allows a max of one site if you replace '*' with a URL. I'd like at least 3.
There are also gems you could use such as rack-cors:
In application.rb:
config.middleware.insert_before 0, "Rack::Cors" do
allow do
origins 'http://myFirstSite.com', 'http://mySecondSite.com', 'http://myThirdSite.com'
resource '/article_pages',
:headers => :any,
:methods => [:post],
:max_age => 0
resource '/article_pages',
:headers => :any,
:methods => [:put],
:max_age => 0
resource '*',
:headers => :any,
:methods => [:get],
:max_age => 0
end
end
This latest example is supposed to only allow the sites specified in origins, but seems to allow any site (as I've tested). I'm not sure if this is a configuration issue but it seems to be configured correctly via their docs.
There are also examples as such: https://gist.github.com/dhoelzgen/cd7126b8652229d32eb4
This one appears to allow ANY site to access the one endpoint inside the controller for which headers['Access-Control-Allow-Origin'] = '*' is set in a filter. Again, I'd like to limit the access to 3 sites.
What is a standard way of allowing only a few sites to access one public endpoint while blocking all other sites?

Sinatra cookies vanish on certain routes?

I have a simple Sinatra app that I am playing with, and for some reason the cookies don't seem to work for certain routes, which I find quite bizarre.
require "sinatra"
set(:authenticate) do |*vars|
condition do
unless request.cookies.has_key?("TestCookie")
redirect to("/login"), 303
end
end
end
get "/login" do
return "No valid cookie"
end
get "/secret", :authenticate => [:auth_cookie] do
cookie = request.cookies["TestCookie"]
return "Secrets ahoy - #{cookie}"
end
get '/cookie/set' do
response.set_cookie("TestCookie", {
:expires => Time.now + 2400,
:value => "TestValue"
})
return "Cookie is set"
end
get '/cookie/get' do
cookie = request.cookies["TestCookie"]
return "Cookie with value #{cookie}"
end
If I go to cookies/set it correctly sets the cookie (can see it in firecookie), then if I go to cookies/get I get the correct cookie output. However if I go to /secret it always redirects to the /login. As I am still fairly new to Ruby syntax I thought it may be a problem with my condition within the authenticate extension, so I have tried removing that and just spitting out the cookie like the other one does. However still nothing, so I am at a loss as to why the cookie is there, I can see it in the browser... and /cookies/get works, but /secret doesn't...
Am I missing something here?
The problem is that the cookie is set with path /cookie. When you set a cookie your can specify a path, which is effectively a sub-part of the Website that you want the cookie to apply to. I guess Sinatra/Rack use the path of the current request by default which in /cookie/set would be /cookie.
You can make it work the way you expect by explicitly specifying the path:
response.set_cookie("TestCookie", {
:expires => Time.now + 2400,
:value => "TestValue",
:path => '/'
})
Or you could set the cookie at a route called say /cookie-set rather than /cookie/set

Sinatra - response.set_cookie doesn't work

I need to use a cookie for my Sinatra application. If I use the simpliest method is works:
response.set_cookie('my_cookie', 'value_of_cookie')
but I need some options such as domain and expire date so I try this:
response.set_cookie("my_cookie", {:value => 'value_of_cookie', :domain => myDomain, :path => myPath, :expires => Date.new})
does not work. No cookie is made. I need this so much....
Please help... thanks!
The documentation on http://sinatra-book.gittr.com/#cookies says to use the set_cookie helper, but in newer versions of Sinatra (at least from 1.2.0+ and possibly earlier), you should use response.set_cookie to set cookies.
response.set_cookie("my_cookie", :value => "value_of_cookie",
:domain => myDomain,
:path => myPath,
:expires => Date.new(2020,1,1))
cookie = request.cookies["my_cookie"]
SUMMARY
don't set localhost as a domain for your cookies because you need to set it to "" or FALSE

How can I persistently overwrite an attribute initialized by Rack::Builder?

I am trying to use OmniAuth to handle the OAuth flow for a small-ish Sinatra app. I can get 37signals Oauth to work perfectly, however I'm trying to create a strategy for Freshbooks Oauth as well.
Unfortunately Freshbooks require OAuth requests to go to a user specific subdomain. I'm acquiring the subdomain as an input and I then need to persistently use the customer specific site URL for all requests.
Here's what I've tried up to now. The problem is that the new site value doesn't persist past the first request.
There's to to be a simple way to achieve this but I'm stumped.
#Here's the setup -
def initialize(app, consumer_key, consumer_secret, subdomain='api')
super(app, :freshbooks, consumer_key, consumer_secret,
:site => "https://"+subdomain+".freshbooks.com",
:signature_method => 'PLAINTEXT',
:request_token_path => "/oauth/oauth_request.php",
:access_token_path => "/oauth/oauth_access.php",
:authorize_path => "/oauth/oauth_authorize.php"
)
end
def request_phase
#Here's the overwrite -
consumer.options[:site] = "https://"+request.env["rack.request.form_hash"]["subdomain"]+".freshbooks.com"
request_token = consumer.get_request_token(:oauth_callback => callback_url)
(session[:oauth]||={})[name.to_sym] = {:callback_confirmed => request_token.callback_confirmed?,
:request_token => request_token.token,
:request_secret => request_token.secret}
r = Rack::Response.new
r.redirect request_token.authorize_url
r.finish
end
Ok, here's a summary of what I did for anyone who comes across this via Google.
I didn't solve the problem in the way I asked it, instead I pushed the subdomain into the session and then I overwrite it whenever the site value needs to be used.
Here's the code:
#Monkeypatching to inject user subdomain
def request_phase
#Subdomain is expected to be submitted as <input name="subdomain">
session[:subdomain] = request.env["rack.request.form_hash"]["subdomain"]
consumer.options[:site] = "https://"+session[:subdomain]+".freshbooks.com"
super
end
#Monkeypatching to inject subdomain again
def callback_phase
consumer.options[:site] = "https://"+session[:subdomain]+".freshbooks.com"
super
end
Note that you still have to set something as the site when it's initialised, otherwise you will get errors due to OAuth not using SSL to make the requests.
If you want to see the actual code I'm using it's at: https://github.com/joeharris76/omniauth I'll push the fork up to the main project once I've battle tested this solution a bit more.

setting cookies

Okay, so I'm trying to set cookies using Ruby. I'm in a Rack environment. response[name]=value will add an HTTP header into the HTTP headers hash rack has. I know that it works.
But the following method of setting cookies doesn't work:
def set_cookie(opts={})
args = {
:name => nil,
:value => nil,
:expires => Time.now+314,
:path => '/',
:domain => Cambium.uri #contains the IP address of the dev server this is running on
}.merge(opts)
raise ArgumentError, ":name and :value are mandatory" if args[:name].nil? or args[:value].nil?
response['Set-Cookie']="#{args[:name]}=#{args[:value]}; expires=#{args[:expires].clone.gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT")}; path=#{args[:path]}; domain=#{args[:domain]}"
end
Why not? And how can I solve it? Thanks.
It turns out that you can't use an IP address with cookies, at least not with also specifying a port.

Resources