mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-09-25 01:53:03 +02:00
fix(admin): stop logging out active long-form work; raise idle timeout to 4h
The admin idle timeout (30 min) was refreshed only by navigations and HTMX requests. During long encoding sessions on an open form there are none, so an actively-typing admin was logged out mid-work after ~30-45 min. Add an activity-driven keepalive: - /admin/session-keepalive.php: 204 when authenticated (refreshes admin_last_activity via AdminAuth::isAuthenticated()), 401 otherwise. - admin-session-keepalive.js: marks activity only on real user input (pointer/keyboard/input/scroll/wheel/touch/focus) and pings at most once per 5 min while the tab is visible. A genuinely idle tab never pings, so the idle timeout still applies. Raise the idle window 30 min -> 4 h: for a single-/few-admin back-office whose main workflow is data entry, 30 min still kicked admins who stepped away mid-form. With the keepalive in place, 4 h means "no interaction at all", not "no navigation". Absolute timeout stays 12 h. Also fix session ID rotation, which never fired: it used `$absolute % IDLE_TIMEOUT_SECONDS === 0`, i.e. required a request to land exactly on a multiple of the interval relative to login time. Replaced with an explicit admin_last_rotation timestamp and a ROTATION_INTERVAL_SECONDS (30 min) constant decoupled from the idle timeout, so raising the idle window does not widen the fixation/replay window. Refactor AdminAuth::enforceSessionTimeout() to return bool instead of redirecting/exiting, so the keepalive endpoint can report 401 cleanly rather than letting fetch follow a redirect to the login page. Smoke test (just smoke-session-keepalive) covers activity refresh, 2 h idle accepted, rotation firing, idle rejection+destruction, and unauthenticated rejection. Docs updated.
This commit is contained in:
+20
-4
@@ -18,13 +18,29 @@ Current security posture for XAMXAM.
|
||||
`Path=/admin`; regenerated on login.
|
||||
- **Session timeouts** (server-side, enforced by `AdminAuth::enforceSessionTimeout()`
|
||||
on every gated request):
|
||||
- **Idle timeout** — 30 min without activity → session destroyed, redirected
|
||||
to login.
|
||||
- **Idle timeout** — 4 h without activity → session destroyed, redirected
|
||||
to login. Raised from 30 min because the back-office's primary workflow is
|
||||
long-form data entry; combined with the activity-driven keepalive below, this
|
||||
means "no interaction at all", not "no navigation".
|
||||
- **Absolute timeout** — 12 h since login → forced re-login, regardless of
|
||||
activity.
|
||||
- Session ID rotated periodically (every 30 min) to limit fixation/replay.
|
||||
- Session ID rotated every 30 min to limit fixation/replay (interval is
|
||||
independent of the idle timeout, so the replay window did not widen when the
|
||||
idle window was raised).
|
||||
- The session *cookie* `lifetime` is 7 days, but that is only an upper bound
|
||||
on browser retention — the actual session is bounded by the two timeouts above.
|
||||
- **Activity-driven keepalive** (`app/public/admin/session-keepalive.php` +
|
||||
`assets/js/app/admin-session-keepalive.js`). `admin_last_activity` is only
|
||||
refreshed by requests the browser makes. A long data-entry session on an open
|
||||
form issues none, so an actively-typing admin used to be logged out after
|
||||
the idle window. The keepalive closes that gap: the client marks activity on real user
|
||||
input (pointer, keyboard, input, scroll, wheel, touch, focus) and pings
|
||||
`/admin/session-keepalive.php` at most once per 5 min while the tab is
|
||||
visible. The endpoint calls `AdminAuth::isAuthenticated()`, which runs the
|
||||
same timeout enforcement and refreshes the timestamp; it returns `204` when
|
||||
authenticated and `401` otherwise (the client then reloads, and the server
|
||||
redirects to login). A tab left open with no interaction never pings, so the
|
||||
idle timeout still applies. Verify with `just smoke-session-keepalive`.
|
||||
- **PHP-FPM session GC tuning** — see below. The app enforces its own timeouts,
|
||||
so `session.gc_maxlifetime` must be ≥ the 12 h absolute timeout or PHP would
|
||||
reap active sessions early.
|
||||
@@ -43,7 +59,7 @@ PHP's own session garbage collector must not reap active sessions before the
|
||||
|
||||
```ini
|
||||
; XAMXAM session tuning.
|
||||
; AdminAuth enforces its own idle/absolute timeouts (30 min / 12 h), so
|
||||
; AdminAuth enforces its own idle/absolute timeouts (4 h / 12 h), so
|
||||
; gc_maxlifetime must be >= the absolute timeout or PHP would reap active
|
||||
; sessions from under the app.
|
||||
session.gc_maxlifetime = 43200
|
||||
|
||||
Reference in New Issue
Block a user