From af8ee109304cb7e9d86cf691b74854f75629f3cf Mon Sep 17 00:00:00 2001 From: fs Date: Mon, 9 Mar 2026 20:47:21 +0100 Subject: [PATCH] feat(auth): keep remember tokens on session timeout - add timeout-specific logout path without remember-token revocation - reset session timers after remember-me auto login - compact session policy wording in admin settings (DE/EN) - extend auth tests for timeout/manual logout behavior --- i18n/default_de.json | 8 +-- i18n/default_en.json | 8 +-- lib/Service/Auth/AuthService.php | 13 ++++ lib/Service/Auth/RememberMeService.php | 3 + pages/admin/settings/index(default).phtml | 1 - tests/Service/Auth/AuthServiceTest.php | 74 ++++++++++++++++++++ tests/Service/Auth/RememberMeServiceTest.php | 8 ++- web/index.php | 2 +- 8 files changed, 106 insertions(+), 11 deletions(-) diff --git a/i18n/default_de.json b/i18n/default_de.json index 3a786f4..658d76c 100644 --- a/i18n/default_de.json +++ b/i18n/default_de.json @@ -1018,10 +1018,10 @@ "Counters": "Zähler", "User lifecycle policy": "Benutzer-Lifecycle-Regel", "Session policy": "Sitzungsrichtlinie", - "Session limits define how long logged-in users stay signed in.": "Sitzungslimits bestimmen, wie lange angemeldete Benutzer eingeloggt bleiben.", - "Idle timeout signs users out after no activity.": "Das Inaktivitäts-Timeout meldet Benutzer bei fehlender Aktivität ab.", + "Session limits define how long logged-in users stay signed in.": "Hier legen Sie Inaktivitäts- und Absolute-Timeout für aktive Sitzungen fest.", + "Idle timeout signs users out after no activity.": "Inaktivitäts-Timeout beendet inaktive Sitzungen, absolutes Timeout beendet lange Sitzungen auch bei Aktivität.", "Absolute timeout signs users out after total session duration, even with activity.": "Das absolute Timeout meldet Benutzer nach der gesamten Sitzungsdauer ab, auch bei Aktivität.", - "New limits apply on each user's next request.": "Neue Limits gelten mit der nächsten Anfrage jedes Benutzers.", + "New limits apply on each user's next request.": "Änderungen gelten ab der nächsten Anfrage je Benutzer. Angemeldet-bleiben-Tokens bleiben aktiv.", "Session idle timeout (minutes)": "Sitzungs-Inaktivitäts-Timeout (Minuten)", "Session absolute timeout (hours)": "Absolutes Sitzungs-Timeout (Stunden)", "Allowed range: 5-1440 minutes (default: 30)": "Erlaubter Bereich: 5-1440 Minuten (Standard: 30)", @@ -1218,7 +1218,7 @@ "Top lifecycle reason codes (7d)": "Top-Lifecycle-Fehlercodes (7d)", "Recent lifecycle risks (7d)": "Aktuelle Lifecycle-Risiken (7d)", "Login progress": "Anmeldefortschritt", - "Your session has expired. Please log in again.": "Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.", + "Your session has expired. Please log in again.": "Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an. Mit „Angemeldet bleiben“ erfolgt die Anmeldung ggf. automatisch.", "Failed to update role permissions": "Rollenberechtigungen konnten nicht aktualisiert werden", "Failed to update permission roles": "Berechtigungsrollen konnten nicht aktualisiert werden" } diff --git a/i18n/default_en.json b/i18n/default_en.json index 09b91b8..da87135 100644 --- a/i18n/default_en.json +++ b/i18n/default_en.json @@ -1018,10 +1018,10 @@ "Counters": "Counters", "User lifecycle policy": "User lifecycle policy", "Session policy": "Session policy", - "Session limits define how long logged-in users stay signed in.": "Session limits define how long logged-in users stay signed in.", - "Idle timeout signs users out after no activity.": "Idle timeout signs users out after no activity.", + "Session limits define how long logged-in users stay signed in.": "Set idle and absolute limits for active sessions.", + "Idle timeout signs users out after no activity.": "Idle timeout ends inactive sessions; absolute timeout ends long sessions even with activity.", "Absolute timeout signs users out after total session duration, even with activity.": "Absolute timeout signs users out after total session duration, even with activity.", - "New limits apply on each user's next request.": "New limits apply on each user's next request.", + "New limits apply on each user's next request.": "Changes apply from each user's next request. Remember-me tokens stay active.", "Session idle timeout (minutes)": "Session idle timeout (minutes)", "Session absolute timeout (hours)": "Session absolute timeout (hours)", "Allowed range: 5-1440 minutes (default: 30)": "Allowed range: 5-1440 minutes (default: 30)", @@ -1218,7 +1218,7 @@ "Top lifecycle reason codes (7d)": "Top lifecycle reason codes (7d)", "Recent lifecycle risks (7d)": "Recent lifecycle risks (7d)", "Login progress": "Login progress", - "Your session has expired. Please log in again.": "Your session has expired. Please log in again.", + "Your session has expired. Please log in again.": "Your session timed out. Please sign in again. If you chose Remember me, sign-in can happen automatically.", "Failed to update role permissions": "Failed to update role permissions", "Failed to update permission roles": "Failed to update permission roles" } diff --git a/lib/Service/Auth/AuthService.php b/lib/Service/Auth/AuthService.php index a82ba28..9155948 100644 --- a/lib/Service/Auth/AuthService.php +++ b/lib/Service/Auth/AuthService.php @@ -268,6 +268,19 @@ class AuthService ]); } + public function logoutDueToSessionTimeout(): void + { + $actorUserId = (int) ($this->sessionUser()['id'] ?? 0); + $actorTenantId = $this->currentTenantIdFromSession(); + $this->sessionStore->remove('session_started_at'); + $this->sessionStore->remove('session_last_activity'); + Auth::logout(); + $this->recordAuthEvent('auth.session.timeout', 'success', [ + 'actor_user_id' => $actorUserId > 0 ? $actorUserId : null, + 'actor_tenant_id' => $actorTenantId > 0 ? $actorTenantId : null, + ]); + } + public function logoutAndRedirect(string $target = 'login'): void { $this->logout(); diff --git a/lib/Service/Auth/RememberMeService.php b/lib/Service/Auth/RememberMeService.php index 3fe90a3..206a97c 100644 --- a/lib/Service/Auth/RememberMeService.php +++ b/lib/Service/Auth/RememberMeService.php @@ -92,6 +92,9 @@ class RememberMeService Session::regenerate(); $this->sessionStore->set('user', $user); + $now = time(); + $this->sessionStore->set('session_started_at', $now); + $this->sessionStore->set('session_last_activity', $now); if (!empty($user['locale'])) { I18n::$locale = (string) $user['locale']; } diff --git a/pages/admin/settings/index(default).phtml b/pages/admin/settings/index(default).phtml index aa8446f..02072f4 100644 --- a/pages/admin/settings/index(default).phtml +++ b/pages/admin/settings/index(default).phtml @@ -334,7 +334,6 @@ $disabledAttr = $isReadOnly ? 'disabled' : ''; -