diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-07-24 17:20:41 +0200 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-07-24 17:20:41 +0200 |
| commit | d5883869e97244335370d54e21ef46b3f1885899 (patch) | |
| tree | 4575a24150f4c2fa98de54fdd556132f0aa0ff8c | |
| parent | 0d2a8e4b61f4b79507519c73f127b7ab883d853c (diff) | |
Give all sessions a uniform absolute lifetime of one week
Enforced at restore via a login-time stamp, written only at genuine
logins so the limit stays absolute rather than sliding. The cookie
name rotation logs everyone out once at deploy. Second-factor users
are deliberately not treated worse than password-only ones.
| -rw-r--r-- | app/controllers/otp_challenges_controller.rb | 1 | ||||
| -rw-r--r-- | app/controllers/sessions_controller.rb | 2 | ||||
| -rw-r--r-- | config/initializers/session_store.rb | 2 | ||||
| -rw-r--r-- | lib/authenticated_system.rb | 9 | ||||
| -rw-r--r-- | lib/authenticated_test_helper.rb | 3 | ||||
| -rw-r--r-- | test/controllers/admin_controller_test.rb | 13 | ||||
| -rw-r--r-- | test/controllers/sessions_controller_test.rb | 1 |
7 files changed, 28 insertions, 3 deletions
diff --git a/app/controllers/otp_challenges_controller.rb b/app/controllers/otp_challenges_controller.rb index 892503a8..e31c36ca 100644 --- a/app/controllers/otp_challenges_controller.rb +++ b/app/controllers/otp_challenges_controller.rb | |||
| @@ -27,6 +27,7 @@ class OtpChallengesController < ApplicationController | |||
| 27 | return_to = session[:return_to] | 27 | return_to = session[:return_to] |
| 28 | reset_session | 28 | reset_session |
| 29 | self.current_user = user | 29 | self.current_user = user |
| 30 | session[:logged_in_at] = Time.now.to_i | ||
| 30 | flash[:notice] = "Logged in successfully" | 31 | flash[:notice] = "Logged in successfully" |
| 31 | redirect_to safe_return_to(return_to, :default => admin_path) | 32 | redirect_to safe_return_to(return_to, :default => admin_path) |
| 32 | else | 33 | else |
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index f0d5cf9b..49d33810 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb | |||
| @@ -29,6 +29,8 @@ class SessionsController < ApplicationController | |||
| 29 | redirect_to new_otp_challenge_path | 29 | redirect_to new_otp_challenge_path |
| 30 | else | 30 | else |
| 31 | self.current_user = user | 31 | self.current_user = user |
| 32 | session[:logged_in_at] = Time.now.to_i | ||
| 33 | |||
| 32 | if user.otp_required? | 34 | if user.otp_required? |
| 33 | flash[:error] = "Your account requires a second factor -- set it up now." | 35 | flash[:error] = "Your account requires a second factor -- set it up now." |
| 34 | redirect_to edit_user_path(user) | 36 | redirect_to edit_user_path(user) |
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 507dc3c4..1835d557 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb | |||
| @@ -1 +1 @@ | |||
| Cccms::Application.config.session_store :cookie_store, :key => '_cccms_session' | Cccms::Application.config.session_store :cookie_store, :key => '_cccms_session_v2' | ||
diff --git a/lib/authenticated_system.rb b/lib/authenticated_system.rb index 7accfaaa..2ec15a77 100644 --- a/lib/authenticated_system.rb +++ b/lib/authenticated_system.rb | |||
| @@ -1,4 +1,6 @@ | |||
| 1 | module AuthenticatedSystem | 1 | module AuthenticatedSystem |
| 2 | SESSION_MAX_AGE = 7.days | ||
| 3 | |||
| 2 | protected | 4 | protected |
| 3 | # Returns true or false if the user is logged in. | 5 | # Returns true or false if the user is logged in. |
| 4 | # Preloads @current_user with the user model if they're logged in. | 6 | # Preloads @current_user with the user model if they're logged in. |
| @@ -98,7 +100,12 @@ module AuthenticatedSystem | |||
| 98 | 100 | ||
| 99 | # Called from #current_user. First attempt to login by the user id stored in the session. | 101 | # Called from #current_user. First attempt to login by the user id stored in the session. |
| 100 | def login_from_session | 102 | def login_from_session |
| 101 | self.current_user = User.find_by_id(session[:user_id]) if session[:user_id] | 103 | return unless session[:user_id] |
| 104 | if session[:logged_in_at].to_i > SESSION_MAX_AGE.ago.to_i | ||
| 105 | self.current_user = User.find_by(:id => session[:user_id]) | ||
| 106 | else | ||
| 107 | session[:user_id] = nil | ||
| 108 | end | ||
| 102 | end | 109 | end |
| 103 | 110 | ||
| 104 | # | 111 | # |
diff --git a/lib/authenticated_test_helper.rb b/lib/authenticated_test_helper.rb index c0ec5f40..8f3a3732 100644 --- a/lib/authenticated_test_helper.rb +++ b/lib/authenticated_test_helper.rb | |||
| @@ -2,5 +2,6 @@ module AuthenticatedTestHelper | |||
| 2 | # Sets the current user in the session from the user fixtures. | 2 | # Sets the current user in the session from the user fixtures. |
| 3 | def login_as(user) | 3 | def login_as(user) |
| 4 | @request.session[:user_id] = user ? users(user).id : nil | 4 | @request.session[:user_id] = user ? users(user).id : nil |
| 5 | end | 5 | @request.session[:logged_in_at] = Time.now.to_i |
| 6 | end | ||
| 6 | end | 7 | end |
diff --git a/test/controllers/admin_controller_test.rb b/test/controllers/admin_controller_test.rb index a177851f..9747d8e9 100644 --- a/test/controllers/admin_controller_test.rb +++ b/test/controllers/admin_controller_test.rb | |||
| @@ -45,4 +45,17 @@ class AdminControllerTest < ActionController::TestCase | |||
| 45 | get :index | 45 | get :index |
| 46 | assert_redirected_to edit_user_path(users(:quentin)) | 46 | assert_redirected_to edit_user_path(users(:quentin)) |
| 47 | end | 47 | end |
| 48 | |||
| 49 | test "a session older than the absolute limit is rejected" do | ||
| 50 | login_as :quentin | ||
| 51 | @request.session[:logged_in_at] = (AuthenticatedSystem::SESSION_MAX_AGE.ago - 1.day).to_i | ||
| 52 | get :index | ||
| 53 | assert_response :redirect | ||
| 54 | end | ||
| 55 | |||
| 56 | test "a fresh session carries the login stamp" do | ||
| 57 | login_as :quentin | ||
| 58 | get :index | ||
| 59 | assert_response :success | ||
| 60 | end | ||
| 48 | end | 61 | end |
diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb index 62acd28a..86da0e61 100644 --- a/test/controllers/sessions_controller_test.rb +++ b/test/controllers/sessions_controller_test.rb | |||
| @@ -9,6 +9,7 @@ class SessionsControllerTest < ActionController::TestCase | |||
| 9 | post :create, params: { login: 'quentin', password: 'monkey' } | 9 | post :create, params: { login: 'quentin', password: 'monkey' } |
| 10 | assert session[:user_id] | 10 | assert session[:user_id] |
| 11 | assert_response :redirect | 11 | assert_response :redirect |
| 12 | assert session[:logged_in_at].present? | ||
| 12 | end | 13 | end |
| 13 | 14 | ||
| 14 | def test_should_fail_login_and_not_redirect | 15 | def test_should_fail_login_and_not_redirect |
