How to set session[:expires] in rack session (Sinatra) - ruby

You can set a session expiry for a Sinatra app when you set up the session engine:
use Rack::Session::Cookie, :expire_after => 60*60*3, :secret => 'xxxx'
But I want to enable a longer session for certain users. Say two weeks.
session[:expires] = ?? #how and where do I put this.?
Do I need to set on each call (before do?) or is once good enough? Is session[:expires] the right thing to set?

First make sure you don't set an expire_after value with the use Rack::Session::Cookie command, then put use Rack::Session::Cookie in your configure block. Next create an "expiry time" variable (let's say expiry_time for this example) or setting in config. Now for each user, when they login, retrieve their expiry_time setting and issue the following command:
env['rack.session.options'].merge! expire_after: expiry_time
That should do what you are asking.
If this doesn't work for you, try putting the env...merge! command in a before block.

I tried to do this via an after filter in Sinatra but it didn't work, I guess it sets the session after after filters have run, so I knocked up a quick Rack filter and it appears to work.
require 'sinatra'
class SessionExpiryModifier
def initialize(app,options={})
#app,#options = app,options
end
def call(env)
warn env["rack.session.options"].inspect
t = Time.now.to_i.even? ? 10 : 60
env["rack.session.options"].merge! :expire_after => 60 * 60 * t
#app.call env
end
end
configure do
use Rack::Session::Cookie,
:expire_after => 60*60*3,
:secret => 'xxxx' * 10
use SessionExpiryModifier
end
get "/" do
session[:usr] = Time.now
env["rack.session.options"].inspect
end
However, that makes it a lot harder to get a conditional from the Sinatra app into the Rack filter to decide on which branch to take, but that depends on what your condition is. Perhaps inject something into the headers that the filter can read to make the decision.

Related

How to setup session based database connections with Sinatra, Sequel and Rack

I am using Sequel with Sinatra in a Puma / Rack server.
I wish to store my Sequel DB connection object with the session - not globally - so that I can have a separate pool of DB connections for each logged on user. The intent is to have one database server logon for each web server logon.
I cannot work out how to do this since the Sequel database object appears to be global singleton. If for example I attempt to serialize the database object and store in the session I will get an error message: TypeError - can't dump anonymous class: I don't want to have to connect to the database on every route request.
How can I do this? Here is some sample code that hopefully indicates what I am trying to achieve:
require 'sinatra/base'
require 'pp'
require 'sequel'
require 'json'
require 'java'
require 'sqljdbc4.jar'
require 'yaml'
class MyApp < Sinatra::Base
set :sessions, true
use Rack::Session::Cookie, :key => 'rack.session',
:expire_after => 2592000,
:secret => 'sydasx'
get '/' do
db = session[:db]
DB = YAML::Load(db)
response = ''
DB['select * from SEC_USER'].each do |row|
response += row.to_s
end
response.to_json
end
get '/login/:username' do
username = params['username']
puts "username: #{username}"
conn_str = "jdbc:sqlserver://localhost:1434;databaseName=#{username};integratedSecurity=true;"
DB = Sequel.connect(conn_str)
puts "DB: #{DB.pretty_inspect}"
db = YAML::dump(DB)
puts "db: #{db}"
session[:db] = db
"logged in"
end
end
You can't serialize the Sequel::Database object. You have a few decent options:
Use a rack middleware that creates a Sequel::Database object per request, using the object only for the request. In this case you wouldn't assign the result of Sequel.connect to a constant, you would pass a block and call the next variable inside that block.
Create a single Sequel::Database object at top level and store it in the DB constant. The the arbitrary_servers and server_block extensions into the Sequel::Database object. Then use a rack middleware that checks out a connection to the appropriate server for the duration of the block.
If you have few customers, it's possible to just use Sequel's sharding support and use just the server_block extension without arbitrary_servers. One advantage of doing that is that connections can be cached so you aren't making a separate database connection per request (which would be the case for both 1. and 2.).
Use a global hash as you mentioned, with keys being user names and values being Sequel::Database objects. You need to make sure you have enough memory to store all the objects you want to track if you do this.

Replace default authentication strategy in Padrino-Warden

Im struggling to replace the default :password strategy for the Padrino-Warden gem.
The way I figure it I need to get a hold of the manager instance for warden so I can set the default strategies but I'm not sure how to do that in the app.rb file
Right now app.rb looks like this
register Padrino::Warden
Warden::Strategies.add(:udid) do
def valid?
puts "udid strat"
params[:udid]
end
def authenticate!
user = User.get(:udid => params[:udid])
user.nil? ? fail!("Could not log in") : success!(user)
end
end
Warden::Manager.serialize_into_session do |user|
user.id
end
Warden::Manager.serialize_from_session do |id|
User.get(id)
end
use ::Warden::Manager do |manager|
manager.scope_defaults :default,
strategies: [:password],
action: 'sessions/unauthenticated'
end
Which does not work. The warden environment still looks like this
Warden::Proxy:70352196940440 #config={:default_scope=>:default, :scope_defaults=>{}, :default_strategies=>{:_all=>[:password]}, :intercept_401=>true, :failure_app=>Dagis}
If I configure warden in config.ru it will set the correct environment for warden but then I suspect the session middleware provided by Padrino does not work well together with Warden.
You need to instruct the warden manager when to use the strategy. Update the strategies key that you're passing to the use method to reflect which strategies you want to enable and the order in which they should be run. You could do this:
use ::Warden::Manager do |manager|
manager.scope_defaults :default,
strategies: [:udid, :password],
action: 'sessions/unauthenticated'
end
There's a bunch of examples on the Warden wiki on github

How to properly start a session using cookies, store it and access it in a Sinatra app using Rack sessions

I have a Sinatra app running on Heroku. I've had a really tough time properly setting up the cookies for my users. I've been looking at examples and documentation for a couple days now and just can't seem to crack the problem. I would like to have my users login with a email and password. If the login is successful, I want to create a cookie which expires after some set amount of time. I want the user to remain logged in if they close and re-open their browser or if I push new code to Heroku. I think that is just normal use of cookies, so if someone can help me get it going, I'd be very grateful.
Here is how I think I'm supposed to setup the post '/login' route.
post '/login/?' do
#do_login is a helper method which checks if the user's credentials are correct
if do_login
use Rack::Session::Cookie, :key => 'rack.session',
:domain => 'www.Feistie.com',
:path => '/',
:expire_after => 2592000,
:secret => 'a_30_character_key',
:user_id => params[:user_id]
end
end
Even if that is correct, I don't really understand it. What is the :key? I'm assuming :domain should just be set to my website as I did. What about :path? Why is :path set to '/'? Is there a place I actually set the secret key for my Rack app? I'm not totally clear on how cookie security works. I'm also not sure if I can add :user_id to the hash the way I did.
Then next part is setting up a helper "logged_in?" to see if someone is logged in?
def logged_in?
!session.nil?
end
That is probably wrong, but I figured I'd try.
I also need to be able to check who the current user actually is. I used to do this by session[:user_id] == #user.id or something similar. The main question I have is how do I access the :user_id value in the cookie?
Finally the logout.
post '/logout/?' do
session.clear
end
If you guys could get me on track with all this, it would be amazing! Thanks in advance and if you need more info, I will be glad to provide.
It's much simpler than that.
http://www.sinatrarb.com/faq.html#sessions
enable :sessions
post '/login/?' do
if do_login
session[:user_id] = params[:user_id]
end
end
post '/logout/?' do
session[:user_id] = nil
end
I generally try to keep session management to the business logic in get, post, before, etc blocks. You can use a before filter to "log-in" the current session's user.
before '*' do
set :user_id, session[:user_id]
end
This is completely optional, but you can put any cookie config in your rackup file:
use Rack::Session::Cookie,
:key => 'rack.session',
:path => '/',
:expire_after => 14400, # In seconds
:secret => 'change_me'
You could just try setting it with response
response.set_cookie "user_id", {:value => params['user_id'],
:expires => (Time.now + 52*7*24*60*60)}
And you can read it back any time before it expires with
request.cookies["user_id"]
To delete the cookie
response.delete_cookie "user_id"
...

What is a very simple authentication scheme for Sinatra/Rack

I am busy porting a very small web app from ASP.NET MVC 2 to Ruby/Sinatra.
In the MVC app, FormsAuthentication.SetAuthCookie was being used to set a persistent cookie when the users login was validated correctly against the database.
I was wondering what the equivalent of Forms Authentication would be in Sinatra? All the authentication frameworks seem very bulky and not really what I'm looking for.
Here is a very simple authentication scheme for Sinatra.
I’ll explain how it works below.
class App < Sinatra::Base
set :sessions => true
register do
def auth (type)
condition do
redirect "/login" unless send("is_#{type}?")
end
end
end
helpers do
def is_user?
#user != nil
end
end
before do
#user = User.get(session[:user_id])
end
get "/" do
"Hello, anonymous."
end
get "/protected", :auth => :user do
"Hello, #{#user.name}."
end
post "/login" do
session[:user_id] = User.authenticate(params).id
end
get "/logout" do
session[:user_id] = nil
end
end
For any route you want to protect, add the :auth => :user condition to it, as in the /protected example above. That will call the auth method, which adds a condition to the route via condition.
The condition calls the is_user? method, which has been defined as a helper. The method should return true or false depending on whether the session contains a valid account id. (Calling helpers dynamically like this makes it simple to add other types of users with different privileges.)
Finally, the before handler sets up a #user instance variable for every request for things like displaying the user’s name at the top of each page. You can also use the is_user? helper in your views to determine if the user is logged in.
Todd's answer does not work for me, and I found an even simpler solution for one-off dead simple authentication in Sinatra's FAQ:
require 'rubygems'
require 'sinatra'
use Rack::Auth::Basic, "Restricted Area" do |username, password|
[username, password] == ['admin', 'admin']
end
get '/' do
"You're welcome"
end
I thought I would share it just in case anyone wandered this question and needed a non-persistent solution.
I' have found this tutorial and repository with a full example, its working fine for me
https://sklise.com/2013/03/08/sinatra-warden-auth/
https://github.com/sklise/sinatra-warden-example
I used the accepted answer for an app that just had 2 passwords, one for users and one for admins. I just made a login form that takes a password(or pin) and compared that to one that I had set in sinatra's settings (one for admin, one for user). Then I set the session[:current_user] to either admin or user according to which password the user entered and authorized accordingly. I didn't even need a user model. I did have to do something like this:
use Rack::Session::Cookie, :key => 'rack.session',
:domain => 'foo.com',
:path => '/',
:expire_after => 2592000, # In seconds
:secret => 'change_me'
As mentioned in the sinatra documentation to get the session to persist in chrome. With that added to my main file, they persist as expected.
I found JWT to be the simple, modern/secure solution I was searching for. OP mentioned bulky frameworks, so for reference I downloaded the tag of the latest jwt gem at the time of writing (2.2.3) and it's 73 KB zipped and 191 KB unzipped. Seems to be well-maintained and open sourced on GitHub.
Here's a good blog post about it with code and a walkthrough for near-beginners: https://auth0.com/blog/ruby-authentication-secure-rack-apps-with-jwt/

Set Rack session cookie expiration programmatically

I'm using Rack to try to implement "Remember Me" functionality in my Sinatra app.
I'm able to set the session cookie to expire when the session ends or in X seconds time but I'd like to do both.
For example, if a user has clicked "remember me" then I wish for their session to end after X seconds. Eg, my app.rb has a line that looks like this:
use Rack::Session::Cookie, :expire_after => 2592000, #30 days in seconds
:secret => MY_SECRET
I've tried to do the following when the user logs in:
if (!remember_me)
env['rack.session.options'][:expire_after] = nil
end
However, this does not set the cookie value.
How to set this?
I was trying to do the exact same thing and I figured out what the problem for me was. The session cookie gets set on every request if you have an expire_after time set. So when you say if (!remember_me), for that request the cookie's expire time gets set to nil. However, on the very next request, the session cookie is reinitialized with an expire time of 2592000. It seems the fix is to not set a default expire_after time and instead say:
# don't set default expire time
use Rack::Session::Cookie, :secret => MY_SECRET
if(remember_me)
env['rack.session.options'][:expire_after] = 2592000
end
I have unfortunately not figured out how to have a default expire_after time and to permanently extend that time programatically.
This probably has to be done before the session is loaded.
See Rack::Session::Cookie#load_session and Rack::Session::Cookie#commit_session
Chris' answer actually didn't work for me. I found that I had to make sure that I included the original session 'options' with the new 'expire_after' value, so instead of:
env['rack.session.options'][:expire_after] = 2592000
I would use:
env['rack.session.options'].merge! expire_after: 2592000
and be sure to put the use Rack::Session::Cookie statement (without an expire_after setting) in you configure block, if you are using Sinatra.
This did the trick.

Resources