devise_session_expirable 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
 
3
3
  All good things come to an end: At least that's how it's supposed to work.
4
4
 
5
- SessionExpirable is an enhanced version of Devise's +timeoutable+ module
5
+ SessionExpirable is an enhanced version of Devise[https://github.com/plataformatec/devise]'s +timeoutable+ module
6
6
  that ensures that no session is allowed to last forever.
7
7
 
8
8
  Like Devise +timeoutable+, SessionExpirable adds a timestamp to sessions
@@ -20,10 +20,19 @@ never be considered as having timed out.
20
20
  In order to prevent the abuse of non-expiring sessions, SessionExpirable
21
21
  treats sessions without timestamps as having already expired.
22
22
 
23
- One other difference with +timeoutable+ is that SessionExpirable
24
- does not support invalidation of authentication tokens from the
25
- devise +token_authenticatable+ module when a request with a valid
26
- authentication token is accompanied by an expired session.
23
+ Another difference is that in the case of a timeout, +timeoutable+
24
+ prevents other authentication strategies from being tried, although
25
+ it has custom logic to allow Devise +rememberable+ to work in spite of a
26
+ timed-out session.
27
+
28
+ SessionExpirable does not allow a timed-out session to interfere with any
29
+ authentication strategies that are configured. One consequence is that
30
+ SessionExpirable does not support invalidation of authentication tokens
31
+ from the Devise +token_authenticatable+ module when a request with a
32
+ valid authentication token is accompanied by an expired session.
33
+
34
+ One final difference is that SessionExpirable does not invoke the Devise
35
+ FailureApp for requests that do not require authentication.
27
36
 
28
37
  == Configuration
29
38
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "devise_session_expirable"
8
- s.version = "0.1.1"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Riley Lynch"]
12
- s.date = "2013-02-16"
12
+ s.date = "2013-03-11"
13
13
  s.description = "devise_session_expirable is an enhanced version of devise's timeoutable module that ensures that no session is allowed to last forever."
14
14
  s.email = "oss+expirable@teleological.net"
15
15
  s.extra_rdoc_files = [
@@ -3,16 +3,11 @@ require 'active_support/core_ext/module/attribute_accessors'
3
3
  require 'active_support/concern'
4
4
  require 'devise'
5
5
 
6
- module Devise #:nodoc:
7
-
8
- mattr_accessor :default_last_request_at
9
- @default_last_request_at = nil
10
-
11
- add_module :session_expirable,
12
- :model => 'devise_session_expirable/model'
13
-
14
- end
6
+ require 'devise_session_expirable/warden_extensions'
7
+ require 'devise_session_expirable/devise_extensions'
15
8
 
16
9
  module DeviseSessionExpirable #:nodoc:
10
+ autoload :Delegator, 'devise_session_expirable/delegator'
11
+ autoload :FailureApp, 'devise_session_expirable/failure_app'
17
12
  end
18
13
 
@@ -0,0 +1,13 @@
1
+
2
+ class DeviseSessionExpirable::Delegator < Devise::Delegator
3
+
4
+ def failure_app(env)
5
+ app = env["warden.options"] &&
6
+ (scope = env["warden.options"][:scope]) &&
7
+ Devise.mappings[scope.to_sym].failure_app
8
+
9
+ app || DeviseSessionExpirable::FailureApp
10
+ end
11
+
12
+ end
13
+
@@ -0,0 +1,38 @@
1
+
2
+ module Devise #:nodoc:
3
+
4
+ mattr_accessor :default_last_request_at
5
+ @default_last_request_at = nil
6
+
7
+ add_module :session_expirable,
8
+ :model => 'devise_session_expirable/model'
9
+
10
+ def self.configure_warden! #:nodoc:
11
+ warden_config.failure_app = DeviseSessionExpirable::Delegator.new
12
+ warden_config.default_scope = Devise.default_scope
13
+ warden_config.intercept_401 = false
14
+
15
+ Devise.mappings.each_value do |mapping|
16
+ warden_config.scope_defaults mapping.name, :strategies => mapping.strategies
17
+ end
18
+
19
+ @@warden_config_block.try :call, Devise.warden_config
20
+ true
21
+ end
22
+
23
+ class Mapping #:nodoc:
24
+
25
+ private
26
+
27
+ def default_failure_app(options)
28
+ @failure_app = options[:failure_app] || DeviseSessionExpirable::FailureApp
29
+ if @failure_app.is_a?(String)
30
+ ref = Devise.ref(@failure_app)
31
+ @failure_app = lambda { |env| ref.get.call(env) }
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+
@@ -0,0 +1,21 @@
1
+
2
+ class DeviseSessionExpirable::FailureApp < Devise::FailureApp
3
+
4
+ def redirect_url
5
+ flash[:timedout] = true if timeout?
6
+ scope_path
7
+ end
8
+
9
+ protected
10
+
11
+ def warden_message
12
+ @message ||=
13
+ warden.message || warden_options[:message] || (timeout? ? :timeout : nil)
14
+ end
15
+
16
+ def timeout?
17
+ !! env['devise.timeout']
18
+ end
19
+
20
+ end
21
+
@@ -1,40 +1,3 @@
1
-
2
- # Each time the user record is fetched from a session, the record is
3
- # consulted (via +#session_expired?+) to determine if the
4
- # +last_request_at+ time in the session is valid, or if the session
5
- # should be considered as having timed out. This validation is not
6
- # performed if +devise.skip_timeout+ is set in the rack environment.
7
- #
8
- # If the session is deemed to have timed out, the record is logged out
9
- # of the session and a +:timeout+ message is thrown, invoking the
10
- # +FailureApp+.
11
- #
12
- # Unlike the Devise +timeoutable+ module, devise_session_expirable does
13
- # not support invalidation of authentication tokens from the devise
14
- # +token_authenticatable+ module when a request with a valid
15
- # authentication token is accompanied by an expired session.
16
-
17
- Warden::Manager.after_fetch do |record, warden, options|
18
- scope = options[:scope]
19
- env = warden.request.env
20
-
21
- puts "after fetch"
22
-
23
- if record &&
24
- record.respond_to?(:session_expired?) &&
25
- warden.authenticated?(scope) &&
26
- options[:store] != false &&
27
- !env['devise.skip_timeout']
28
-
29
- last_request_at = warden.session(scope)['last_request_at']
30
- if record.session_expired?(last_request_at)
31
- puts "expired!"
32
- warden.logout(scope)
33
- throw :warden, :scope => scope, :message => :timeout
34
- end
35
- end
36
- end
37
-
38
1
  # Each time the user record is set, the +last_request_at+ time
39
2
  # is updated in the scoped session. This update is not performed if
40
3
  # devise.skip_trackable is set in the rack environment.
@@ -43,16 +6,12 @@ Warden::Manager.after_set_user do |record, warden, options|
43
6
  scope = options[:scope]
44
7
  env = warden.request.env
45
8
 
46
- puts "after set_user"
47
-
48
9
  if record &&
49
10
  record.respond_to?(:session_expired?) &&
50
11
  warden.authenticated?(scope) &&
51
12
  options[:store] != false &&
52
13
  !env['devise.skip_trackable']
53
14
 
54
- puts "reset"
55
-
56
15
  warden.session(scope)['last_request_at'] = Time.now.utc
57
16
  end
58
17
  end
@@ -34,11 +34,6 @@ module Devise #:nodoc:
34
34
  # is set.
35
35
 
36
36
  def session_expired?(last_access)
37
- puts "last_access #{last_access}"
38
- puts "timeout_in #{timeout_in}"
39
- puts "deadline #{timeout_in.ago}"
40
- puts !timeout_in.nil? && (!last_access || last_access <= timeout_in.ago)
41
-
42
37
  last_access ||= default_last_request_at
43
38
  return false if remember_exists_and_not_expired?
44
39
  !timeout_in.nil? && (!last_access || last_access <= timeout_in.ago)
@@ -0,0 +1,46 @@
1
+
2
+ # Each time the user record is fetched from a session, the record is
3
+ # consulted (via +#session_expired?+) to determine if the
4
+ # +last_request_at+ time in the session is valid, or if the session
5
+ # should be considered as having timed out. If the session is deemed
6
+ # to have timed out, the record is disregarded.
7
+ #
8
+ # Unlike the Devise +timeoutable+ module, devise_session_expirable does
9
+ # not support invalidation of authentication tokens from the devise
10
+ # +token_authenticatable+ module when a request with a valid
11
+ # authentication token is accompanied by an expired session.
12
+
13
+ class Warden::SessionSerializer
14
+
15
+ def fetch(scope)
16
+ key = session[key_for(scope)]
17
+ return nil unless key
18
+
19
+ method_name = "#{scope}_deserialize"
20
+ user = respond_to?(method_name) ? send(method_name, key) : deserialize(key)
21
+ user = nil unless valid_for_deserialization?(scope, user)
22
+ delete(scope) unless user
23
+ user
24
+ end
25
+
26
+ private
27
+
28
+ def valid_for_deserialization?(scope, user)
29
+ ! validate_session_expiration(scope, user)
30
+ end
31
+
32
+ def validate_session_expiration(scope, user)
33
+ is_expired = false
34
+ if user && user.respond_to?(:session_expired?)
35
+ last_request_at = session_for_scope(scope)['last_request_at']
36
+ is_expired = user.session_expired?(last_request_at)
37
+ end
38
+ env['devise.timeout'] = is_expired
39
+ end
40
+
41
+ def session_for_scope(scope)
42
+ session["warden.user.#{scope}.session"] ||= {}
43
+ end
44
+
45
+ end
46
+
@@ -43,7 +43,7 @@ class SessionExpirableIntegrationTest < ActionDispatch::IntegrationTest
43
43
  get clear_timeout_user_path(user)
44
44
 
45
45
  get users_path
46
- assert_redirected_to users_path
46
+ assert_redirected_to new_user_session_path
47
47
  assert_not warden.authenticated?(:user)
48
48
  end
49
49
 
@@ -53,27 +53,39 @@ class SessionExpirableIntegrationTest < ActionDispatch::IntegrationTest
53
53
  assert_not_nil last_request_at
54
54
 
55
55
  get users_path
56
- assert_redirected_to users_path
56
+ assert_redirected_to new_user_session_path
57
57
  assert_not warden.authenticated?(:user)
58
58
  end
59
59
 
60
- test 'time out is not triggered on sign out' do
60
+ test 'sign out when timed out is ignored' do
61
61
  user = sign_in_as_user
62
62
  get expire_user_path(user)
63
63
 
64
64
  get destroy_user_session_path
65
-
66
65
  assert_response :redirect
67
66
  assert_redirected_to root_path
67
+ assert_not warden.authenticated?(:user)
68
68
  follow_redirect!
69
- assert_contain 'Signed out successfully'
69
+ assert_not_contain 'Signed out successfully'
70
+ end
71
+
72
+ test 'expired session is not extended by sign in page' do
73
+ user = sign_in_as_user
74
+ get expire_user_path(user)
75
+ assert warden.authenticated?(:user)
76
+
77
+ get "/users/sign_in"
78
+ assert_response :success
79
+ assert_contain 'Sign in'
80
+ assert_not warden.authenticated?(:user)
70
81
  end
71
82
 
72
83
  test 'time out is not triggered on sign in' do
73
84
  user = sign_in_as_user
74
85
  get expire_user_path(user)
75
86
 
76
- post "/users/sign_in", :email => user.email, :password => "123456"
87
+ post "/users/sign_in",
88
+ :user => { :email => user.email, :password => "12345678" }
77
89
 
78
90
  assert_response :redirect
79
91
  follow_redirect!
@@ -90,7 +102,7 @@ class SessionExpirableIntegrationTest < ActionDispatch::IntegrationTest
90
102
 
91
103
  begin
92
104
  get admins_path
93
- assert_redirected_to admins_path
105
+ assert_redirected_to new_admin_session_path
94
106
  assert_not warden.authenticated?(:admin)
95
107
  ensure
96
108
  Admin.send(:remove_method, :reset_authentication_token!)
@@ -108,25 +120,12 @@ class SessionExpirableIntegrationTest < ActionDispatch::IntegrationTest
108
120
 
109
121
  get expire_user_path(user)
110
122
  get users_path
111
- assert_redirected_to users_path
123
+ assert_redirected_to new_user_session_path
112
124
  assert_not warden.authenticated?(:user)
113
125
  end
114
126
  end
115
127
 
116
128
  test 'error message with i18n' do
117
- store_translations :en, :devise => {
118
- :failure => { :user => { :timeout => 'Session expired!' } }
119
- } do
120
- user = sign_in_as_user
121
-
122
- get expire_user_path(user)
123
- get root_path
124
- follow_redirect!
125
- assert_contain 'Session expired!'
126
- end
127
- end
128
-
129
- test 'error message with i18n with double redirect' do
130
129
  store_translations :en, :devise => {
131
130
  :failure => { :user => { :timeout => 'Session expired!' } }
132
131
  } do
@@ -135,7 +134,6 @@ class SessionExpirableIntegrationTest < ActionDispatch::IntegrationTest
135
134
  get expire_user_path(user)
136
135
  get users_path
137
136
  follow_redirect!
138
- follow_redirect!
139
137
  assert_contain 'Session expired!'
140
138
  end
141
139
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise_session_expirable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-16 00:00:00.000000000 Z
12
+ date: 2013-03-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -108,8 +108,12 @@ files:
108
108
  - VERSION
109
109
  - devise_session_expirable.gemspec
110
110
  - lib/devise_session_expirable.rb
111
+ - lib/devise_session_expirable/delegator.rb
112
+ - lib/devise_session_expirable/devise_extensions.rb
113
+ - lib/devise_session_expirable/failure_app.rb
111
114
  - lib/devise_session_expirable/hook.rb
112
115
  - lib/devise_session_expirable/model.rb
116
+ - lib/devise_session_expirable/warden_extensions.rb
113
117
  - test/integration/session_expirable_test.rb
114
118
  - test/models/session_expirable_test.rb
115
119
  - test/orm/active_record.rb
@@ -168,6 +172,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
172
  - - ! '>='
169
173
  - !ruby/object:Gem::Version
170
174
  version: '0'
175
+ segments:
176
+ - 0
177
+ hash: 1721273659600446097
171
178
  required_rubygems_version: !ruby/object:Gem::Requirement
172
179
  none: false
173
180
  requirements: