Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

added server side time_to_live of session data #8

Merged
merged 3 commits into from

2 participants

Christian Meier Jon
Christian Meier

added server side time_to_live of session data, when the session_data is expires the session gets reset.

first I thinking to implement it on application level, but I think the right place it the here (#7)

I know that invasive regarding adding a default timeout of 30 minutes but since the version is 0.0.4 I guess that is OK. security should start restrictive IMO.

Jon
Collaborator

This seems to result in a session that will expire after thirty minutes, irrespective of activity in the session. Would it be better to have a 'rolling' timeout so you're logged out for inactivity, rather than just logged out?

Christian Meier

yes, the was the intention. I guess I messed it up when I added the per session timeout. let me see the code . . .

Christian Meier

the session actually lives on the client. for each request from the client the "timeout timestamp" wiil be set afresh inside the session (load_session method deletes the timestamp, commit_session sets it)). when the client side sits inactive for 30 minutes the next request to the server will reset the session since it timed out.

I guess that is what you meant with rolling timeout.

probably the naming is not clear enough in code + descriptions - at tleast the spec I adjusted to show this behaviour.

Jon
Collaborator

Ah, missed the session.delete. Brain parsed it as session.fetch. Which doesn't even make sense, I know.

Christian Meier

any chance to get this in. what can I do or improve it to see it accepted ? really would like use this feature in production on my side !

Jon
Collaborator

Apologies, I forgot about this.

I like the functionality and I think it's useful enough to have it implemented at the middleware level. I don't like how long it makes the methods, with nested if's. Not that they were the nicest and neatest before this change, so maybe I'm being unreasonable.

Christian Meier

it looks clearer now and easier to understand ;)

Jon
Collaborator

Oh, that looks a lot clearer :+1:

I'll merge when I get home this evening, probably.

Christian Meier
Jon namelessjon merged commit 2138c04 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 21, 2014
  1. Christian Meier

    added server side time_to_live of session data, when the session_data…

    mkristian authored
    … is expires the session gets reset.
  2. Christian Meier

    make the spec clearer on the fact that each request will reset the ti…

    mkristian authored
    …meout-timer for the session
Commits on Jun 11, 2014
  1. Christian Meier

    straighten up the logic flow

    mkristian authored
This page is out of date. Refresh to see the latest.
Showing with 60 additions and 15 deletions.
  1. +36 −14 lib/encrypted_cookie.rb
  2. +24 −1 spec/encrypted_cookie_spec.rb
50 lib/encrypted_cookie.rb
View
@@ -20,14 +20,22 @@ module Session
# :domain => 'foo.com',
# :path => '/',
# :expire_after => 2592000
+ # :time_to_live => 600
#
# All parameters are optional except :secret.
#
+ # The default for the session time-to-live is 30 minutes. You can set
+ # the timeout on per session base by adding the expiration time in the
+ # session:
+ # session[Rack::Session::EncryptedCookie::EXPIRES] = Time.now + 120
+ #
# Note that you shouldn't trust the expire_after parameter in the cookie
- # for session expiry as that can be altered by the recipient. Instead,
- # store a timestamp in the session
+ # for session expiry as that can be altered by the recipient. Instead,
+ # use time_to_live which is server side check.
class EncryptedCookie
+ EXPIRES = '_encrypted_cookie_expires_'
+
def initialize(app, options={})
@app = app
@key = options[:key] || "rack.session"
@@ -35,6 +43,7 @@ def initialize(app, options={})
fail "Error! A secret is required to use encrypted cookies. Do something like this:\n\nuse Rack::Session::EncryptedCookie, :secret => YOUR_VERY_LONG_VERY_RANDOM_SECRET_KEY_HERE" unless @secret
@default_options = {:domain => nil,
:path => "/",
+ :time_to_live => 1800,
:expire_after => nil}.merge(options)
@encryptor = Encryptor.new(@secret)
end
@@ -47,32 +56,45 @@ def call(env)
private
+ def remove_expiration(session_data)
+ expires = session_data.delete(EXPIRES)
+ if expires and expires < Time.now
+ session_data.clear
+ end
+ end
+
def load_session(env)
request = Rack::Request.new(env)
+ env["rack.session.options"] = @default_options.dup
+
session_data = request.cookies[@key]
+ session_data = @encryptor.decrypt(session_data)
+ session_data = Marshal.load(session_data)
+ remove_expiration(session_data)
- if session_data
- session_data = @encryptor.decrypt(session_data)
- end
+ env["rack.session"] = session_data
+ rescue
+ env["rack.session"] = Hash.new
+ end
- begin
- session_data = Marshal.load(session_data)
- env["rack.session"] = session_data
- rescue
- env["rack.session"] = Hash.new
+ def add_expiration(session_data, options)
+ if options[:time_to_live] && !session_data.key?(EXPIRES)
+ expires = Time.now + options[:time_to_live]
+ session_data.merge!({EXPIRES => expires})
end
-
- env["rack.session.options"] = @default_options.dup
end
def commit_session(env, status, headers, body)
- session_data = Marshal.dump(env["rack.session"])
+ options = env["rack.session.options"]
+
+ session_data = env["rack.session"]
+ add_expiration(session_data, options)
+ session_data = Marshal.dump(session_data)
session_data = @encryptor.encrypt(session_data)
if session_data.size > (4096 - @key.size)
env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.")
else
- options = env["rack.session.options"]
cookie = Hash.new
cookie[:value] = session_data
cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
25 spec/encrypted_cookie_spec.rb
View
@@ -23,10 +23,14 @@ def unpack_cookie
class EncryptedApp < Sinatra::Application
disable :show_exceptions
- use Rack::Session::EncryptedCookie, :secret => 'foo' * 10
+ use Rack::Session::EncryptedCookie, :secret => 'foo' * 10, :time_to_live => 0.1
get '/' do
"session: " + session.inspect
end
+ get '/timeout/:value' do
+ session[Rack::Session::EncryptedCookie::EXPIRES] = Time.now + params[:value].to_i
+ "timeout set"
+ end
get '/set/:key/:value' do
session[params[:key]] = params[:value]
"all set"
@@ -99,6 +103,25 @@ def app
get '/'
last_response.body.should == 'session: {}'
end
+ it "should reset the session on timeout" do
+ get '/set/foo/bar'
+ last_response.body.should == 'all set'
+ get '/'
+ last_response.body.should == 'session: {"foo"=>"bar"}'
+ sleep 0.08
+ get '/'
+ last_response.body.should == 'session: {"foo"=>"bar"}'
+ sleep 0.08
+ get '/'
+ last_response.body.should == 'session: {"foo"=>"bar"}'
+ sleep 0.1
+ get '/'
+ last_response.body.should == 'session: {}'
+ get '/timeout/0'
+ last_response.body.should == 'timeout set'
+ get '/'
+ last_response.body.should == 'session: {}'
+ end
end
describe UnencryptedApp do
Something went wrong with that request. Please try again.