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.
(heure de dépôt was showing UTC)
- feat: add date_depot column (real TFE deposit date)
with CSV round-trip + Brussels→UTC sanitization
- fix: keep PHP default tz at UTC to preserve token/share-link
expiry consistency; convert to Brussels only in db_datetime()
script-src 'self' 'unsafe-inline' added to admin Content-Security-Policy.
default-src 'self' was blocking OverType editor init block and
the dev live-reload poller. Admin section is auth-gated so
unsafe-inline is acceptable.
Silence mkdir() with @ operator; guard file_put_contents with
is_writable() check. When storage/cache/rate_limit is not writable
by php-fpm, requests are allowed through instead of throwing
warnings that flood the nginx error log.