dev/index.php
2026-01-14 08:49:06 +00:00

358 lines
16 KiB
PHP

<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
session_set_cookie_params([
'secure' => true,
'httponly' => true,
'samesite' => 'Strict'
]);
session_start();
// ========================
// CONFIGURATION
// ========================
require_once __DIR__ . '/config/db.php';
require_once __DIR__ . '/config/env.php';
require_once __DIR__ . '/lib/RateLimit.php';
require_once __DIR__ . '/lib/FileValidator.php';
$rateLimit = new RateLimit($pdo);
$fileValidator = new FileValidator();
$message = '';
// Initialiser le token CSRF
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// ========================
// INSCRIPTION
// ========================
if (isset($_POST['register'])) {
// Vérifier CSRF
if (empty($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
$message = 'Erreur de sécurité (CSRF)';
} else {
$username = trim($_POST['registerUsername'] ?? '');
$password = $_POST['registerPassword'] ?? '';
if (strlen($username) < 3 || strlen($username) > 50) {
$message = 'Le pseudo doit faire entre 3 et 50 caractères';
} elseif (strlen($password) < 8) {
$message = 'Le mot de passe doit faire au moins 8 caractères';
} elseif (!preg_match('/^[a-zA-Z0-9_-]+$/', $username)) {
$message = 'Le pseudo ne peut contenir que des lettres, chiffres, - et _';
} else {
// Vérifier si le pseudo existe déjà
$stmt = $pdo->prepare("SELECT id FROM `{$env['TABLE_USERS']}` WHERE pseudo = ?");
$stmt->execute([$username]);
if ($stmt->fetch()) {
$message = 'Pseudo déjà utilisé';
} else {
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
$stmt = $pdo->prepare(
"INSERT INTO `{$env['TABLE_USERS']}`
(pseudo, mot_de_passe, role, date_inscription)
VALUES (?, ?, 'user', NOW())"
);
$stmt->execute([$username, $hash]);
$message = 'Inscription réussie ! Connectez-vous.';
}
}
}
}
// ========================
// CONNEXION
// ========================
if (isset($_POST['login'])) {
// Vérifier CSRF
if (empty($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
$message = 'Erreur de sécurité (CSRF)';
} else {
$username = trim($_POST['loginUsername'] ?? '');
$password = $_POST['loginPassword'] ?? '';
$clientIp = $_SERVER['REMOTE_ADDR'];
// Rate limiting
if ($rateLimit->isBlocked($clientIp, 'login', 5, 900)) {
$message = 'Trop de tentatives. Réessayez dans 15 minutes.';
} else {
$stmt = $pdo->prepare(
"SELECT * FROM `{$env['TABLE_USERS']}` WHERE pseudo = ?"
);
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user || !password_verify($password, $user['mot_de_passe'])) {
$rateLimit->recordAttempt($clientIp, 'login');
$message = 'Identifiants incorrects';
} else {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['pseudo'];
$_SESSION['role'] = $user['role'];
$message = 'Connexion réussie !';
}
}
}
}
// ========================
// DÉCONNEXION
// ========================
if (isset($_POST['logout'])) {
// Vérifier CSRF
if (empty($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
$message = 'Erreur de sécurité (CSRF)';
} else {
$_SESSION = [];
session_destroy();
header("Location: " . $_SERVER['PHP_SELF']);
exit;
}
}
// ========================
// CRÉATION DE POST
// ========================
if (isset($_POST['createPost'])) {
// Vérifier CSRF
if (empty($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
$message = 'Erreur de sécurité (CSRF)';
} elseif (!isset($_SESSION['user_id'])) {
$message = 'Vous devez être connecté pour poster';
} else {
$userId = $_SESSION['user_id'];
// Rate limiting : 1 post par minute
if ($rateLimit->isBlocked($userId, 'post', 1, 60)) {
$message = 'Vous ne pouvez poster qu\'une fois par minute. Patientez...';
} else {
$content = trim($_POST['postContent'] ?? '');
if (!$content) {
$message = 'Le contenu du post est vide';
} elseif (strlen($content) > 5000) {
$message = 'Le post ne doit pas dépasser 5000 caractères';
} else {
// Insérer le post
$stmt = $pdo->prepare(
"INSERT INTO `{$env['TABLE_MESSAGES']}`
(id_utilisateur, contenu, date_creation)
VALUES (?, ?, NOW())"
);
$stmt->execute([$userId, $content]);
$postId = $pdo->lastInsertId();
// Gérer les fichiers
if (!empty($_FILES['postImage']['tmp_name'])) {
$file = $_FILES['postImage'];
$validationResult = $fileValidator->validate($file, 2 * 1024 * 1024);
if ($validationResult['valid']) {
// Vérifier la sécurité du fichier
if ($fileValidator->isSafe($file['tmp_name'])) {
// Créer le répertoire uploads s'il n'existe pas
$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
// Générer un nom de fichier sécurisé
$fileName = uniqid('post_', true) . '.' . $validationResult['ext'];
$filePath = $uploadDir . $fileName;
// Vérifier les permissions
if (is_writable($uploadDir)) {
// Déplacer le fichier
if (move_uploaded_file($file['tmp_name'], $filePath)) {
// Sauvegarder dans la DB
$stmt = $pdo->prepare(
"INSERT INTO `{$env['TABLE_FILES']}`
(id_message, nom_fichier, chemin_fichier, taille, type_mime, date_upload)
VALUES (?, ?, ?, ?, ?, NOW())"
);
$stmt->execute([
$postId,
$file['name'],
'/uploads/' . $fileName,
$file['size'],
$validationResult['mime']
]);
$message = 'Post publié avec succès !';
} else {
$message = 'Erreur lors de l\'upload';
}
} else {
$message = 'Erreur lors du téléchargement du fichier';
}
} else {
$message = 'Le fichier contient du contenu dangereux';
}
} else {
$message = $validationResult['error'];
}
} else {
$message = 'Post publié avec succès !';
}
// Enregistrer l'activité
$rateLimit->recordAttempt($userId, 'post');
}
}
}
}
// ========================
// CHARGEMENT DES POSTS
// ========================
$stmt = $pdo->query(
"SELECT m.*, u.pseudo, f.chemin_fichier, f.nom_fichier, f.taille
FROM `{$env['TABLE_MESSAGES']}` m
JOIN `{$env['TABLE_USERS']}` u ON m.id_utilisateur = u.id
LEFT JOIN `{$env['TABLE_FILES']}` f ON m.id = f.id_message
ORDER BY m.date_creation DESC"
);
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Si l'utilisateur est admin, charger le flag
$flag = null;
if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin') {
$flag = $env['FLAG'];
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Forum Équipe J</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; }
.container { max-width: 800px; margin: 0 auto; }
.header { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 5px 20px rgba(0,0,0,0.1); text-align: center; margin-bottom: 30px; }
.header h1 { color: #333; margin-bottom: 10px; }
.header p { color: #666; }
.flag-box { background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-bottom: 20px; border-radius: 5px; }
.flag-box strong { color: #856404; }
.auth-section { background: white; padding: 25px; border-radius: 10px; box-shadow: 0 5px 20px rgba(0,0,0,0.1); margin-bottom: 30px; }
.auth-section h2 { color: #333; margin-bottom: 15px; font-size: 18px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; color: #555; font-weight: bold; }
.form-group input, .form-group textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; }
.form-group textarea { resize: vertical; min-height: 100px; }
.btn { padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; margin-right: 10px; }
.btn:hover { background: #5568d3; }
.btn-danger { background: #dc3545; }
.btn-danger:hover { background: #c82333; }
.message { padding: 15px; border-radius: 5px; margin-bottom: 20px; text-align: center; font-weight: bold; }
.message.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.message.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.post { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 5px 20px rgba(0,0,0,0.1); margin-bottom: 20px; }
.post-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; border-bottom: 1px solid #eee; padding-bottom: 10px; }
.post-author { color: #667eea; font-weight: bold; }
.post-date { color: #999; font-size: 12px; }
.post-content { color: #333; line-height: 1.6; margin-bottom: 15px; }
.post-image { max-width: 100%; border-radius: 5px; margin-top: 10px; }
.no-posts { text-align: center; color: #999; padding: 40px; }
.user-status { color: #667eea; font-weight: bold; margin-bottom: 20px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🏁 FLAG : <?php echo $flag ? htmlspecialchars($flag, ENT_QUOTES, 'UTF-8') : ''; ?></h1>
<p>🚀 Forum Équipe J</p>
<p>Espace de discussion sécurisé</p>
</div>
<?php if ($message): ?>
<div class="message <?php echo (strpos($message, 'réussie') !== false || strpos($message, 'succès') !== false) ? 'success' : 'error'; ?>">
<?php echo htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['user_id'])): ?>
<div class="user-status">
👤 Connecté en tant que : <?php echo htmlspecialchars($_SESSION['username'], ENT_QUOTES, 'UTF-8'); ?>
<?php if ($_SESSION['role'] === 'admin'): ?>
<span style="color: #ffc107;">[ADMIN]</span>
<?php endif; ?>
</div>
<div class="auth-section">
<h2>✍️ Créer un post</h2>
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<div class="form-group">
<label>Commentaire:</label>
<textarea name="postContent" required></textarea>
</div>
<div class="form-group">
<label>Image (PNG ou JPEG, min 2Mo):</label>
<input type="file" name="postImage" accept="image/png,image/jpeg">
</div>
<button type="submit" name="createPost" class="btn">📤 Publier</button>
<button type="submit" name="logout" class="btn btn-danger">🚪 Déconnexion</button>
</form>
</div>
<?php else: ?>
<div class="auth-section">
<h2>Connexion</h2>
<form method="POST">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<div class="form-group">
<label>Pseudo:</label>
<input type="text" name="loginUsername" required>
</div>
<div class="form-group">
<label>Mot de passe:</label>
<input type="password" name="loginPassword" required>
</div>
<button type="submit" name="login" class="btn">Se connecter</button>
</form>
</div>
<div class="auth-section">
<h2>Inscription</h2>
<form method="POST">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<div class="form-group">
<label>Pseudo:</label>
<input type="text" name="registerUsername" required>
</div>
<div class="form-group">
<label>Mot de passe:</label>
<input type="password" name="registerPassword" required>
</div>
<button type="submit" name="register" class="btn">S'inscrire</button>
</form>
</div>
<?php endif; ?>
<h2 style="color: white; margin-top: 30px; margin-bottom: 20px;">💬 Commentaires récents</h2>
<?php if (empty($posts)): ?>
<div class="no-posts">📭 Aucun post pour le moment. Soyez le premier à poster !</div>
<?php else: ?>
<?php foreach ($posts as $post): ?>
<div class="post">
<div class="post-header">
<span class="post-author">👤 <?php echo htmlspecialchars($post['pseudo'], ENT_QUOTES, 'UTF-8'); ?></span>
<span class="post-date"><?php echo htmlspecialchars($post['date_creation'], ENT_QUOTES, 'UTF-8'); ?></span>
</div>
<div class="post-content">
<?php echo nl2br(htmlspecialchars($post['contenu'], ENT_QUOTES, 'UTF-8')); ?>
</div>
<?php if ($post['chemin_fichier']): ?>
<img src="<?php echo htmlspecialchars($post['chemin_fichier'], ENT_QUOTES, 'UTF-8'); ?>" alt="Post image" class="post-image">
<small style="color: #999;">📎 <?php echo htmlspecialchars($post['nom_fichier'], ENT_QUOTES, 'UTF-8'); ?> (<?php echo round($post['taille'] / 1024 / 1024, 2); ?> Mo)</small>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</body>
</html>