0 && ($now - $last) > $maxIdleSeconds) { logout_and_redirect_to_login($_SERVER['REQUEST_URI'] ?? '/'); } $_SESSION['last_activity'] = $now; } /** Anti open-redirect : n’autorise que les chemins internes commençant par '/' */ function next_safe(?string $u): ?string { if (!$u) return null; if (!preg_match('#^/[^\\r\\n]*$#', $u)) return null; if (strpos($u, '//') === 0) return null; return $u; } function redirect_forbidden(?string $requestUri = null): void { $u = $requestUri ?: ($_SERVER['REQUEST_URI'] ?? '/'); $u = next_safe($u) ?? '/'; header('Location: /403.php?next=' . rawurlencode($u), true, 303); exit; } function redirect_to_login_with_next(?string $requestUri = null): void { $u = $requestUri ?: ($_SERVER['REQUEST_URI'] ?? '/'); $u = next_safe($u) ?? '/'; $_SESSION['redirect_to'] = $u; header('Location: /connexion.php?next=' . rawurlencode($u), true, 303); exit; } function logout_and_redirect_to_login(?string $requestUri = null): void { $_SESSION = []; if (session_status() === PHP_SESSION_ACTIVE) { $params = session_get_cookie_params(); setcookie( session_name(), '', time() - 42000, $params['path'] ?? '/', $params['domain'] ?? '', (bool)($params['secure'] ?? false), (bool)($params['httponly'] ?? true) ); } if (session_status() === PHP_SESSION_ACTIVE) { session_destroy(); } redirect_to_login_with_next($requestUri ?? ($_SERVER['REQUEST_URI'] ?? '/')); } /** * 🔒 Re-check rôle depuis DB (optionnel) + hydrate $_SESSION['role'] si absent. * Retourne le rôle (string) ou '' si introuvable. */ function refresh_role_from_db(PDO $pdo): string { $compteId = (int)($_SESSION['compte_id'] ?? 0); if ($compteId <= 0) return ''; $stmt = $pdo->prepare("SELECT role FROM compte WHERE id=? LIMIT 1"); $stmt->execute([$compteId]); $role = (string)($stmt->fetchColumn() ?: ''); if ($role !== '') { $_SESSION['role'] = $role; } return strtolower($role); } /** * ✅ À appeler en haut des pages protégées. * - $allowedRoles = null => tous rôles * - options: * - 'pdo' => PDO (pour re-check DB si demandé) * - 'recheck_db_role' => true/false * - 'admin_strict_reference' => int|null (ex: 88888888) */ function require_login_or_redirect(?array $allowedRoles = null, array $options = []): void { if (!is_logged_in()) { redirect_to_login_with_next($_SERVER['REQUEST_URI'] ?? '/'); } enforce_session_timeout(7200); $pdo = $options['pdo'] ?? ($GLOBALS['pdo'] ?? null); $recheck = (bool)($options['recheck_db_role'] ?? false); // Rôle: session d'abord, DB si demandé (ou si role manquant) $userRole = strtolower((string)($_SESSION['role'] ?? '')); if (($userRole === '' || $recheck) && ($pdo instanceof PDO)) { $userRole = refresh_role_from_db($pdo); } if ($allowedRoles) { $allowed = array_map(static fn($r) => strtolower((string)$r), $allowedRoles); if (!in_array($userRole, $allowed, true)) { redirect_forbidden($_SERVER['REQUEST_URI'] ?? '/'); } // 🔥 Renfort admin optionnel : role + reference_id if (in_array('admin', $allowed, true)) { $strictRef = $options['admin_strict_reference'] ?? null; if ($strictRef !== null) { $refId = (int)($_SESSION['reference_id'] ?? 0); if ($refId !== (int)$strictRef) { redirect_forbidden($_SERVER['REQUEST_URI'] ?? '/'); } } } } } /* ========================= Admin helpers (CSRF + logs) ========================= */ /** Token CSRF session */ function ensure_csrf_token(): string { if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return (string)$_SESSION['csrf_token']; } /** Exige POST + CSRF (supporte aussi header X-CSRF-Token si tu veux fetch/AJAX) */ function require_post_csrf(): void { if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') { http_response_code(405); exit('Méthode non autorisée'); } $token = $_POST['csrf_token'] ?? ''; if ($token === '' && !empty($_SERVER['HTTP_X_CSRF_TOKEN'])) { $token = (string)$_SERVER['HTTP_X_CSRF_TOKEN']; } if ($token === '' || !hash_equals((string)($_SESSION['csrf_token'] ?? ''), (string)$token)) { http_response_code(419); exit('CSRF invalide'); } } /** Log admin minimal */ function log_admin_action(PDO $pdo, int $userId, string $action, string $details = ''): void { // limite simple pour éviter d'exploser la table if (strlen($details) > 2000) $details = substr($details, 0, 2000); $pdo->prepare( "INSERT INTO admin_actions_log (user_id, action, details, ip) VALUES (?,?,?,?)" )->execute([ $userId, $action, $details, $_SERVER['REMOTE_ADDR'] ?? '' ]); }