mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
maintenance: allow /partage through gate, fix fragment routing, add visibility table in admin
Extract shared filepond logic into src/FilepondHandler.php class. Admin filepond endpoints delegate to the handler after AdminAuth check. New partage filepond endpoints at /partage/actions/filepond/ verify share_active session flag + CSRF token, no admin auth required. JS reads filepond-base meta tag to determine endpoint path: - Admin pages: /admin/actions/filepond (via head.php isAdmin check) - Partage form: /partage/actions/filepond (explicit meta) partage/index.php sets share_active = true on form render, cleans up on successful submit. Partage process endpoint rate-limited to 30/5min per session. No nginx changes needed — /partage/ location already handles PHP without auth_basic.
This commit is contained in:
35
app/public/partage/actions/filepond/load.php
Normal file
35
app/public/partage/actions/filepond/load.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* FilePond load endpoint — streams an existing thesis file back to FilePond (partage).
|
||||
*
|
||||
* GET /partage/actions/filepond/load.php?id={db_id}
|
||||
*
|
||||
* Auth: requires an active partage session (share_active flag).
|
||||
*
|
||||
* Used in edit mode to restore saved files into the FilePond UI.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../../bootstrap.php';
|
||||
require_once __DIR__ . '/../../../../src/FilepondHandler.php';
|
||||
|
||||
// ── Start session ────────────────────────────────────────────────────────
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
$isSecure = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'secure' => $isSecure,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
session_start();
|
||||
}
|
||||
|
||||
// ── Auth: must have an active partage session ────────────────────────────
|
||||
if (empty($_SESSION['share_active'])) {
|
||||
http_response_code(403);
|
||||
die('Accès refusé.');
|
||||
}
|
||||
|
||||
$handler = new FilepondHandler('[filepond:partage]');
|
||||
$handler->handleLoad();
|
||||
56
app/public/partage/actions/filepond/process.php
Normal file
56
app/public/partage/actions/filepond/process.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* FilePond process endpoint — receives one file per request (partage).
|
||||
*
|
||||
* POST /partage/actions/filepond/process.php
|
||||
* Headers: X-CSRF-Token
|
||||
* Fields: file (multipart), queue_type (string)
|
||||
*
|
||||
* Auth: requires an active partage session (share_active flag) + CSRF token.
|
||||
*
|
||||
* Returns plain text file_id on success (200), or error message on failure (4xx).
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../../bootstrap.php';
|
||||
require_once __DIR__ . '/../../../../src/FilepondHandler.php';
|
||||
|
||||
// ── Start session ────────────────────────────────────────────────────────
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
$isSecure = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'secure' => $isSecure,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
session_start();
|
||||
}
|
||||
|
||||
// ── Auth: must have an active partage session ────────────────────────────
|
||||
if (empty($_SESSION['share_active'])) {
|
||||
http_response_code(403);
|
||||
die('Accès refusé.');
|
||||
}
|
||||
|
||||
// ── Rate limit: 30 uploads per 5 min per session ────────────────────────
|
||||
require_once APP_ROOT . '/src/RateLimit.php';
|
||||
$rateLimit = new RateLimit(30, 300, STORAGE_ROOT . '/cache/rate_limit');
|
||||
$rateLimitId = 'fp_share_process_' . session_id();
|
||||
if (!$rateLimit->checkKey($rateLimitId)) {
|
||||
error_log('[filepond:partage:process] Rate limit hit');
|
||||
http_response_code(429);
|
||||
die('Trop de requêtes. Veuillez patienter.');
|
||||
}
|
||||
|
||||
// ── CSRF via header ──────────────────────────────────────────────────────
|
||||
$csrfHeader = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
|
||||
if (!isset($_SESSION['csrf_token'])
|
||||
|| !hash_equals($_SESSION['csrf_token'], $csrfHeader)) {
|
||||
error_log('[filepond:partage:process] CSRF FAIL');
|
||||
http_response_code(403);
|
||||
die('Token CSRF invalide.');
|
||||
}
|
||||
|
||||
$handler = new FilepondHandler('[filepond:partage]');
|
||||
$handler->handleProcess();
|
||||
42
app/public/partage/actions/filepond/remove.php
Normal file
42
app/public/partage/actions/filepond/remove.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* FilePond remove endpoint — soft-deletes a thesis_files row (partage).
|
||||
*
|
||||
* DELETE /partage/actions/filepond/remove.php
|
||||
* Body: JSON { "db_id": 123 }
|
||||
*
|
||||
* Auth: requires an active partage session (share_active flag) + CSRF token.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../../bootstrap.php';
|
||||
require_once __DIR__ . '/../../../../src/FilepondHandler.php';
|
||||
|
||||
// ── Start session ────────────────────────────────────────────────────────
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
$isSecure = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'secure' => $isSecure,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
session_start();
|
||||
}
|
||||
|
||||
// ── Auth: must have an active partage session ────────────────────────────
|
||||
if (empty($_SESSION['share_active'])) {
|
||||
http_response_code(403);
|
||||
die('Accès refusé.');
|
||||
}
|
||||
|
||||
// ── CSRF via header ──────────────────────────────────────────────────────
|
||||
$csrfHeader = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
|
||||
if (!isset($_SESSION['csrf_token'])
|
||||
|| !hash_equals($_SESSION['csrf_token'], $csrfHeader)) {
|
||||
http_response_code(403);
|
||||
die('Token CSRF invalide.');
|
||||
}
|
||||
|
||||
$handler = new FilepondHandler('[filepond:partage]');
|
||||
$handler->handleRemove();
|
||||
42
app/public/partage/actions/filepond/revert.php
Normal file
42
app/public/partage/actions/filepond/revert.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* FilePond revert endpoint — deletes a just-uploaded tmp file (partage).
|
||||
*
|
||||
* DELETE /partage/actions/filepond/revert.php
|
||||
* Body: plain text file_id
|
||||
*
|
||||
* Auth: requires an active partage session (share_active flag) + CSRF token.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../../bootstrap.php';
|
||||
require_once __DIR__ . '/../../../../src/FilepondHandler.php';
|
||||
|
||||
// ── Start session ────────────────────────────────────────────────────────
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
$isSecure = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'secure' => $isSecure,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
session_start();
|
||||
}
|
||||
|
||||
// ── Auth: must have an active partage session ────────────────────────────
|
||||
if (empty($_SESSION['share_active'])) {
|
||||
http_response_code(403);
|
||||
die('Accès refusé.');
|
||||
}
|
||||
|
||||
// ── CSRF via header ──────────────────────────────────────────────────────
|
||||
$csrfHeader = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
|
||||
if (!isset($_SESSION['csrf_token'])
|
||||
|| !hash_equals($_SESSION['csrf_token'], $csrfHeader)) {
|
||||
http_response_code(403);
|
||||
die('Token CSRF invalide.');
|
||||
}
|
||||
|
||||
$handler = new FilepondHandler('[filepond:partage]');
|
||||
$handler->handleRevert();
|
||||
@@ -23,10 +23,11 @@ $slug = $parts[0] ?? '';
|
||||
$action = $parts[1] ?? '';
|
||||
|
||||
// Special route: /partage/fragments/* (HTMX fragments under fragments/ subdirectory)
|
||||
if (str_starts_with($slug, 'fragments/') && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($slug === 'fragments' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
App::boot();
|
||||
$fragmentFile = __DIR__ . '/' . basename($slug);
|
||||
if (file_exists($fragmentFile)) {
|
||||
$fragmentBase = $action;
|
||||
$fragmentFile = __DIR__ . '/fragments/' . $fragmentBase;
|
||||
if ($fragmentBase !== '' && file_exists($fragmentFile)) {
|
||||
require_once $fragmentFile;
|
||||
} else {
|
||||
http_response_code(404);
|
||||
@@ -133,6 +134,7 @@ if (!$validationResult['valid']) {
|
||||
// If already verified in session, skip the gate and render the form directly
|
||||
if (!empty($_SESSION['share_verified_' . $slug])) {
|
||||
error_log('[ShareLink] Session already verified for slug=' . $slug . ', rendering form');
|
||||
$_SESSION['share_active'] = true;
|
||||
$link = $validationResult['link'];
|
||||
renderShareLinkForm($slug, $link);
|
||||
exit;
|
||||
@@ -151,6 +153,7 @@ if (!$validationResult['valid']) {
|
||||
}
|
||||
|
||||
// Link is valid - render the form
|
||||
$_SESSION['share_active'] = true;
|
||||
$link = $validationResult['link'];
|
||||
renderShareLinkForm($slug, $link);
|
||||
|
||||
@@ -217,6 +220,7 @@ function requirePasswordGate(array $link, string $slug): void
|
||||
if ($shareLinkModel->verifyPassword($link, $_POST['share_password'])) {
|
||||
// Store verified status in session
|
||||
$_SESSION['share_verified_' . $slug] = true;
|
||||
$_SESSION['share_active'] = true;
|
||||
error_log('[ShareLink] Password verified OK for slug=' . $slug . ', redirecting to form');
|
||||
// Redirect to clear POST data
|
||||
header('Location: /partage/' . $slug);
|
||||
@@ -417,6 +421,7 @@ function renderShareLinkForm(string $slug, array $link): void
|
||||
<?php if (!empty($_SESSION['csrf_token'])): ?>
|
||||
<meta name="csrf-token" content="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<?php endif; ?>
|
||||
<meta name="filepond-base" content="/partage/actions/filepond">
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/common.css') ?>">
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/form.css') ?>">
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/filepond.min.css') ?>">
|
||||
@@ -511,6 +516,7 @@ function handleShareLinkSubmission(string $slug): void
|
||||
if (isset($_POST['share_password_submit'])) {
|
||||
if ($shareLinkModel->verifyPassword($link, $_POST['share_password_submit'])) {
|
||||
$_SESSION['share_verified_' . $slug] = true;
|
||||
$_SESSION['share_active'] = true;
|
||||
} else {
|
||||
$_SESSION['_flash_error'] = 'Mot de passe incorrect.';
|
||||
header('Location: /partage/' . urlencode($slug));
|
||||
@@ -565,6 +571,7 @@ function handleShareLinkSubmission(string $slug): void
|
||||
// Clean up share-specific session data
|
||||
unset($_SESSION[$shareCsrfKey]);
|
||||
unset($_SESSION['share_verified_' . $slug]);
|
||||
unset($_SESSION['share_active']);
|
||||
|
||||
// Send confirmation e-mail - on delivery failure, redirect to retry page
|
||||
$emailError = null;
|
||||
|
||||
Reference in New Issue
Block a user