Files
Pontoporeia 554ba3ee8d 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.
2026-09-18 16:26:49 +02:00
..

Admin Panel Structure

This directory and app/templates/admin/ contain the admin panel for managing the XAMXAM TFE database.

Entry points (app/public/admin/)

File Purpose
index.php List all theses (main page; hosts the inline CSV import + tabs for list/trash)
add.php Add new thesis form
edit.php Existing thesis form
recapitulatif.php Post-submission recap
cleanup.php Cleanup page (Corbeille — restore/delete trashed files)
system.php System dashboard (logs, SMTP/PeerTube status)
contenus.php Editable content (pages, contacts)
contenus-edit.php Edit a content page
acces.php Share-link management
file-access.php Restricted-file access requests
media.php Admin file viewer — opens files of 'Interdit' (access_type_id=3) theses; session-gated (AdminAuth::requireLogin), delegates to MediaController::handle(adminBypass: true)
media-viewer.php HTML wrapper that opens a thesis file with a reliable tab title (original file name); embeds the file via media.php in a full-viewport iframe
account.php Admin account / password
session-keepalive.php Lightweight 204/401 liveness ping that refreshes admin_last_activity while an open admin page is being actively used (see docs/security.md)
login.php Login (session)
import.php Redirects to /admin/ (CSV import is inline in index.php)
status.php, markdown-cheatsheet-fragment.php, *fragment.php HTMX fragments / helpers

Backend actions (app/public/admin/actions/)

Process forms and redirect (no HTML output):

  • formulaire.php — thesis create submission (ThesisCreateController::submit())
  • edit.php — thesis edit submission (ThesisEditController::save())
  • export-csv.php, export-db.php, export-files.php — see docs/export.md
  • filepond/ — FilePond async upload endpoints
  • many others: publish, delete, corbeille (trash), draft, visibility, tag, language, form-help*, page, apropos, smtp-test, peertube-*, maintenance, settings, account, access-request, acces-etudiante, cleanup-*

Templates

View templates live under app/templates/admin/ (not in public/):

  • app/templates/admin/*.php — page layouts
  • app/templates/admin/partials/ — shared fragments (toasts, dialogs, toc, …)

The public/partage and form partials live in app/templates/partials/ and app/templates/partage/.

Auth

  • PHP session auth (src/AdminAuth.php) via AdminAuth::requireLogin() is the only authentication layer. The old nginx auth_basic layer has been removed — see docs/security.md and nginx/docs/PHP_AUTH_LAYER.md.
  • All forms include a CSRF token from $_SESSION['csrf_token'].
  • Inputs use PDO prepared statements; uploads validated and stored outside the webroot (app/storage/).

Bootstrap / routing

Entry pages bootstrap the app and set up the environment:

require_once __DIR__ . '/../../bootstrap.php';      // defines APP_ROOT, autoload, config
require_once APP_ROOT . '/src/AdminAuth.php';
AdminAuth::requireLogin();

APP_ROOT is the app/ directory. Database access is via app/src/Database.php; form logic lives in app/src/Controllers/ and app/src/Form/.

URL structure

  • /admin/ — list theses (index.php)
  • /admin/add.php — add thesis
  • /admin/edit.php?id=N — edit thesis
  • /admin/cleanup.php, /admin/system.php, /admin/acces.php, etc.
  • /admin/actions/… — backend processors

Development guide

Add a page: create app/templates/admin/yourpage.php, add a thin app/public/admin/yourpage.php entry that bootstraps + requires the template, and add navigation in app/templates/admin/partials/admin-toc.php.

Add an action: create app/public/admin/actions/youraction.php that bootstraps, requires login, verifies the CSRF token, performs the work, and redirects back to the referring admin page.

See docs/development.md for the general workflow (dev server, builds, tests, linting).