This commit is contained in:
Flatlogic Bot 2026-04-07 17:47:38 +00:00
parent f55942bf0d
commit a26f2122a4
11 changed files with 1430 additions and 349 deletions

0
.perm_test_apache Normal file
View File

0
.perm_test_exec Normal file
View File

624
admin.php Normal file
View File

@ -0,0 +1,624 @@
<?php
require_once __DIR__ . '/db/auth.php';
auth_start_session();
auth_bootstrap();
if (!auth_is_admin()) {
header('Location: index.php');
exit;
}
$flash = auth_flash_get();
$flash_type = $flash['type'] ?? '';
$flash_message = $flash['message'] ?? '';
$edit_cl_auth_id = isset($_GET['edit']) ? (int) $_GET['edit'] : 0;
$edit_cl_auth_user = '';
$edit_cl_auth_right = 'member';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$csrf_token = isset($_POST['csrf_token']) ? (string) $_POST['csrf_token'] : null;
if (!auth_validate_csrf($csrf_token)) {
auth_flash_set('error', 'Jeton CSRF invalide.');
header('Location: admin.php');
exit;
}
$admin_action = (string) ($_POST['admin_action'] ?? '');
if ($admin_action === 'create') {
$submitted_cl_auth_user = trim((string) ($_POST['cl_auth_user'] ?? ''));
$submitted_cl_auth_pass = (string) ($_POST['cl_auth_pass'] ?? '');
$submitted_cl_auth_right = (string) ($_POST['cl_auth_right'] ?? 'member');
if ($submitted_cl_auth_user === '' || $submitted_cl_auth_pass === '') {
auth_flash_set('error', 'Le login et le mot de passe sont obligatoires.');
header('Location: admin.php');
exit;
}
if (!in_array($submitted_cl_auth_right, ['admin', 'member'], true)) {
auth_flash_set('error', 'Droit utilisateur invalide.');
header('Location: admin.php');
exit;
}
$stmt_duplicate_user = db()->prepare('SELECT COUNT(*) FROM tbl_auth WHERE cl_auth_user = :cl_auth_user');
$stmt_duplicate_user->execute([
'cl_auth_user' => $submitted_cl_auth_user,
]);
$cl_auth_user_total = (int) $stmt_duplicate_user->fetchColumn();
if ($cl_auth_user_total > 0) {
auth_flash_set('error', 'Ce login existe déjà.');
header('Location: admin.php');
exit;
}
$cl_auth_user = $submitted_cl_auth_user;
$cl_auth_pass = password_hash($submitted_cl_auth_pass, PASSWORD_DEFAULT);
$cl_auth_right = $submitted_cl_auth_right;
$stmt_create_user = db()->prepare(
'INSERT INTO tbl_auth (cl_auth_user, cl_auth_pass, cl_auth_right) VALUES (:cl_auth_user, :cl_auth_pass, :cl_auth_right)'
);
$stmt_create_user->execute([
'cl_auth_user' => $cl_auth_user,
'cl_auth_pass' => $cl_auth_pass,
'cl_auth_right' => $cl_auth_right,
]);
auth_flash_set('success', 'Compte créé avec succès.');
header('Location: admin.php');
exit;
}
if ($admin_action === 'update') {
$cl_auth_id = (int) ($_POST['cl_auth_id'] ?? 0);
$submitted_cl_auth_user = trim((string) ($_POST['cl_auth_user'] ?? ''));
$submitted_cl_auth_pass = (string) ($_POST['cl_auth_pass'] ?? '');
$submitted_cl_auth_right = (string) ($_POST['cl_auth_right'] ?? 'member');
if ($cl_auth_id <= 0 || $submitted_cl_auth_user === '') {
auth_flash_set('error', 'Données de modification invalides.');
header('Location: admin.php');
exit;
}
if (!in_array($submitted_cl_auth_right, ['admin', 'member'], true)) {
auth_flash_set('error', 'Droit utilisateur invalide.');
header('Location: admin.php?edit=' . $cl_auth_id);
exit;
}
$stmt_tbl_auth = db()->prepare('SELECT cl_auth_id, cl_auth_user, cl_auth_pass, cl_auth_right FROM tbl_auth WHERE cl_auth_id = :cl_auth_id LIMIT 1');
$stmt_tbl_auth->execute([
'cl_auth_id' => $cl_auth_id,
]);
$tbl_auth = $stmt_tbl_auth->fetch();
if (!$tbl_auth) {
auth_flash_set('error', 'Utilisateur introuvable.');
header('Location: admin.php');
exit;
}
$current_cl_auth_id = (int) $tbl_auth['cl_auth_id'];
$current_cl_auth_user = (string) $tbl_auth['cl_auth_user'];
$current_cl_auth_pass = (string) $tbl_auth['cl_auth_pass'];
$current_cl_auth_right = (string) $tbl_auth['cl_auth_right'];
unset($current_cl_auth_id, $current_cl_auth_user);
$stmt_duplicate_user = db()->prepare(
'SELECT COUNT(*) FROM tbl_auth WHERE cl_auth_user = :cl_auth_user AND cl_auth_id <> :cl_auth_id'
);
$stmt_duplicate_user->execute([
'cl_auth_user' => $submitted_cl_auth_user,
'cl_auth_id' => $cl_auth_id,
]);
$cl_auth_user_total = (int) $stmt_duplicate_user->fetchColumn();
if ($cl_auth_user_total > 0) {
auth_flash_set('error', 'Ce login existe déjà.');
header('Location: admin.php?edit=' . $cl_auth_id);
exit;
}
if ($current_cl_auth_right === 'admin' && $submitted_cl_auth_right !== 'admin') {
$stmt_admin_total = db()->query("SELECT COUNT(*) FROM tbl_auth WHERE cl_auth_right = 'admin'");
$cl_auth_admin_total = (int) $stmt_admin_total->fetchColumn();
if ($cl_auth_admin_total <= 1) {
auth_flash_set('error', 'Impossible de rétrograder le dernier administrateur.');
header('Location: admin.php?edit=' . $cl_auth_id);
exit;
}
}
$cl_auth_user = $submitted_cl_auth_user;
$cl_auth_right = $submitted_cl_auth_right;
$cl_auth_pass = $current_cl_auth_pass;
if ($submitted_cl_auth_pass !== '') {
$cl_auth_pass = password_hash($submitted_cl_auth_pass, PASSWORD_DEFAULT);
}
$stmt_update_user = db()->prepare(
'UPDATE tbl_auth
SET cl_auth_user = :cl_auth_user,
cl_auth_pass = :cl_auth_pass,
cl_auth_right = :cl_auth_right
WHERE cl_auth_id = :cl_auth_id'
);
$stmt_update_user->execute([
'cl_auth_user' => $cl_auth_user,
'cl_auth_pass' => $cl_auth_pass,
'cl_auth_right' => $cl_auth_right,
'cl_auth_id' => $cl_auth_id,
]);
if (isset($_SESSION['user']) && $_SESSION['user'] === $tbl_auth['cl_auth_user']) {
$_SESSION['user'] = $cl_auth_user;
$_SESSION['role'] = $cl_auth_right;
}
auth_flash_set('success', 'Compte modifié avec succès.');
header('Location: admin.php');
exit;
}
if ($admin_action === 'delete') {
$cl_auth_id = (int) ($_POST['cl_auth_id'] ?? 0);
if ($cl_auth_id <= 0) {
auth_flash_set('error', 'Suppression impossible.');
header('Location: admin.php');
exit;
}
$stmt_tbl_auth = db()->prepare('SELECT cl_auth_id, cl_auth_user, cl_auth_pass, cl_auth_right FROM tbl_auth WHERE cl_auth_id = :cl_auth_id LIMIT 1');
$stmt_tbl_auth->execute([
'cl_auth_id' => $cl_auth_id,
]);
$tbl_auth = $stmt_tbl_auth->fetch();
if (!$tbl_auth) {
auth_flash_set('error', 'Utilisateur introuvable.');
header('Location: admin.php');
exit;
}
$cl_auth_user = (string) $tbl_auth['cl_auth_user'];
$cl_auth_pass = (string) $tbl_auth['cl_auth_pass'];
$cl_auth_right = (string) $tbl_auth['cl_auth_right'];
unset($cl_auth_pass);
if ($cl_auth_right === 'admin') {
$stmt_admin_total = db()->query("SELECT COUNT(*) FROM tbl_auth WHERE cl_auth_right = 'admin'");
$cl_auth_admin_total = (int) $stmt_admin_total->fetchColumn();
if ($cl_auth_admin_total <= 1) {
auth_flash_set('error', 'Impossible de supprimer le dernier administrateur.');
header('Location: admin.php');
exit;
}
}
$stmt_delete_user = db()->prepare('DELETE FROM tbl_auth WHERE cl_auth_id = :cl_auth_id');
$stmt_delete_user->execute([
'cl_auth_id' => $cl_auth_id,
]);
if (isset($_SESSION['user']) && $_SESSION['user'] === $cl_auth_user) {
header('Location: logout.php');
exit;
}
auth_flash_set('success', 'Compte supprimé avec succès.');
header('Location: admin.php');
exit;
}
}
if ($edit_cl_auth_id > 0) {
$stmt_edit_user = db()->prepare('SELECT cl_auth_id, cl_auth_user, cl_auth_pass, cl_auth_right FROM tbl_auth WHERE cl_auth_id = :cl_auth_id LIMIT 1');
$stmt_edit_user->execute([
'cl_auth_id' => $edit_cl_auth_id,
]);
$tbl_auth = $stmt_edit_user->fetch();
if ($tbl_auth) {
$edit_cl_auth_id = (int) $tbl_auth['cl_auth_id'];
$edit_cl_auth_user = (string) $tbl_auth['cl_auth_user'];
$edit_cl_auth_pass = (string) $tbl_auth['cl_auth_pass'];
$edit_cl_auth_right = (string) $tbl_auth['cl_auth_right'];
unset($edit_cl_auth_pass);
} else {
$edit_cl_auth_id = 0;
auth_flash_set('error', 'Utilisateur introuvable.');
header('Location: admin.php');
exit;
}
}
$stmt_users = db()->query('SELECT cl_auth_id, cl_auth_user, cl_auth_pass, cl_auth_right FROM tbl_auth ORDER BY cl_auth_user ASC');
$tbl_auth_all = $stmt_users->fetchAll();
$csrf_token = auth_csrf_token();
[$default_admin_user, $default_admin_password] = auth_default_admin_credentials();
$current_session_user = isset($_SESSION['user']) ? (string) $_SESSION['user'] : '';
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Administration Sécure | R.E.A.C.T.</title>
<link rel="stylesheet" type="text/css" href="css/styles.css">
<link rel="stylesheet" type="text/css" href="css/default.css">
<style>
:root {
--primary: #a29b78;
--primary-glow: rgba(162, 155, 120, 0.4);
--bg-dark: #080a0f;
--card-bg: rgba(20, 24, 33, 0.85);
--border-glow: rgba(162, 155, 120, 0.25);
--danger: #ff4d4d;
--success: #00ff88;
}
body {
background: radial-gradient(circle at top right, #1a1f2e, var(--bg-dark));
background-attachment: fixed;
color: #e0e0e0;
font-family: 'Electrolize', sans-serif;
margin: 0;
overflow-x: hidden;
min-height: 100vh;
}
.admin-layout {
display: flex;
flex-direction: column;
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.admin-topbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem 2rem;
background: var(--card-bg);
backdrop-filter: blur(10px);
border: 1px solid var(--border-glow);
border-radius: 12px;
margin-bottom: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.topbar-info h1 {
margin: 0;
font-size: 1.5rem;
letter-spacing: 2px;
text-transform: uppercase;
background: linear-gradient(90deg, #fff, var(--primary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.topbar-info p {
margin: 0.25rem 0 0;
font-size: 0.85rem;
color: var(--primary);
opacity: 0.8;
}
.topbar-actions {
display: flex;
gap: 1rem;
}
.btn-modern {
padding: 0.6rem 1.2rem;
border: 1px solid var(--primary);
background: transparent;
color: #fff;
font-family: 'Electrolize', sans-serif;
font-size: 0.9rem;
text-transform: uppercase;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 4px;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
}
.btn-modern:hover {
background: var(--primary);
color: var(--bg-dark);
box-shadow: 0 0 15px var(--primary-glow);
}
.btn-modern.danger {
border-color: var(--danger);
color: var(--danger);
}
.btn-modern.danger:hover {
background: var(--danger);
color: #fff;
box-shadow: 0 0 15px rgba(255, 77, 77, 0.3);
}
.admin-content {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 2rem;
}
@media (max-width: 1024px) {
.admin-content { grid-template-columns: 1fr; }
}
.glass-card {
background: var(--card-bg);
backdrop-filter: blur(12px);
border: 1px solid var(--border-glow);
border-radius: 12px;
padding: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
.glass-card h2 {
margin-top: 0;
margin-bottom: 1.5rem;
font-size: 1.25rem;
color: var(--primary);
border-bottom: 1px solid var(--border-glow);
padding-bottom: 0.75rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.85rem;
color: #aaa;
text-transform: uppercase;
}
.form-control {
width: 100%;
padding: 0.8rem 1rem;
background: rgba(0, 0, 0, 0.3);
border: 1px solid #444;
border-radius: 4px;
color: #fff;
font-family: 'Electrolize', sans-serif;
transition: border-color 0.3s;
}
.form-control:focus {
outline: none;
border-color: var(--primary);
background: rgba(0, 0, 0, 0.5);
}
.modern-table {
width: 100%;
border-collapse: separate;
border-spacing: 0 8px;
}
.modern-table th {
text-align: left;
padding: 1rem;
font-size: 0.8rem;
text-transform: uppercase;
color: var(--primary);
opacity: 0.7;
}
.modern-table td {
padding: 1rem;
background: rgba(255, 255, 255, 0.03);
border-top: 1px solid rgba(255, 255, 255, 0.05);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.modern-table td:first-child {
border-left: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 8px 0 0 8px;
}
.modern-table td:last-child {
border-right: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 0 8px 8px 0;
}
.modern-table tr:hover td {
background: rgba(162, 155, 120, 0.05);
}
.badge {
padding: 0.25rem 0.6rem;
border-radius: 4px;
font-size: 0.75rem;
text-transform: uppercase;
}
.badge-admin { background: rgba(162, 155, 120, 0.2); color: var(--primary); border: 1px solid var(--primary); }
.badge-member { background: rgba(255, 255, 255, 0.1); color: #ccc; border: 1px solid #555; }
.flash {
padding: 1rem 1.5rem;
border-radius: 8px;
margin-bottom: 1.5rem;
font-size: 0.9rem;
border-left: 4px solid var(--primary);
background: rgba(162, 155, 120, 0.1);
}
.flash.error { border-color: var(--danger); background: rgba(255, 77, 77, 0.1); color: #ffbaba; }
.flash.success { border-color: var(--success); background: rgba(0, 255, 136, 0.1); color: #baffda; }
.row-actions {
display: flex;
gap: 0.5rem;
}
.btn-mini {
padding: 0.3rem 0.6rem;
font-size: 0.75rem;
}
.user-id {
font-family: monospace;
color: var(--primary);
font-weight: bold;
}
.empty-state {
padding: 2rem;
text-align: center;
color: #666;
font-style: italic;
}
</style>
</head>
<body>
<div class="admin-layout">
<header class="admin-topbar">
<div class="topbar-info">
<h1>R.E.A.C.T. Core Admin</h1>
<p>Niveau d\'accès : <strong>Administrateur</strong> | Session : <strong><?php echo htmlspecialchars($current_session_user, ENT_QUOTES, 'UTF-8'); ?></strong></p>
</div>
<div class="topbar-actions">
<a href="index.php" class="btn-modern">Retour au site</a>
<a href="logout.php" class="btn-modern danger">Session End</a>
</div>
</header>
<?php if ($flash_message !== ''): ?>
<div class="flash <?php echo htmlspecialchars($flash_type, ENT_QUOTES, 'UTF-8'); ?>">
<?php echo htmlspecialchars($flash_message, ENT_QUOTES, 'UTF-8'); ?>
</div>
<?php endif; ?>
<?php if ($default_admin_user === 'admin'): ?>
<div class="flash">
<strong style="color: var(--primary);">Sécurité critique :</strong> Les identifiants par défaut sont actifs.
(<code><?php echo htmlspecialchars($default_admin_user, ENT_QUOTES, 'UTF-8'); ?></code> / <code><?php echo htmlspecialchars($default_admin_password, ENT_QUOTES, 'UTF-8'); ?></code>)
<br><small>Veuillez modifier ces accès dès maintenant.</small>
</div>
<?php endif; ?>
<main class="admin-content">
<!-- Form Card -->
<section class="glass-card">
<h2><?php echo $edit_cl_auth_id > 0 ? 'Mise à jour sujet' : 'Nouveau sujet'; ?></h2>
<form method="post" action="admin.php<?php echo $edit_cl_auth_id > 0 ? '?edit=' . $edit_cl_auth_id : ''; ?>">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="admin_action" value="<?php echo $edit_cl_auth_id > 0 ? 'update' : 'create'; ?>">
<?php if ($edit_cl_auth_id > 0):
?><input type="hidden" name="cl_auth_id" value="<?php echo $edit_cl_auth_id; ?>">
<?php endif; ?>
<div class="form-group">
<label for="cl_auth_user">Identifiant Système</label>
<input class="form-control" id="cl_auth_user" name="cl_auth_user" type="text" required value="<?php echo htmlspecialchars($edit_cl_auth_user, ENT_QUOTES, 'UTF-8'); ?>" placeholder="Nom d\'utilisateur">
</div>
<div class="form-group">
<label for="cl_auth_pass"><?php echo $edit_cl_auth_id > 0 ? 'Nouveau Secret (vide = inchangé)' : 'Secret d\'accès'; ?></label>
<input class="form-control" id="cl_auth_pass" name="cl_auth_pass" type="password" <?php echo $edit_cl_auth_id > 0 ? '' : 'required'; ?> placeholder="••••••••">
</div>
<div class="form-group">
<label for="cl_auth_right">Niveau d\'accréditation</label>
<select class="form-control" id="cl_auth_right" name="cl_auth_right">
<option value="admin" <?php echo $edit_cl_auth_right === 'admin' ? 'selected' : ''; ?>>Admin</option>
<option value="member" <?php echo $edit_cl_auth_right === 'member' ? 'selected' : ''; ?>>Membre</option>
</select>
</div>
<div style="display: flex; gap: 10px; margin-top: 2rem;">
<button class="btn-modern" style="flex: 2;" type="submit">
<?php echo $edit_cl_auth_id > 0 ? 'Appliquer' : 'Initialiser'; ?>
</button>
<?php if ($edit_cl_auth_id > 0):
?><a class="btn-modern danger" style="flex: 1;" href="admin.php">Annuler</a>
<?php endif; ?>
</div>
</form>
</section>
<!-- Table Card -->
<section class="glass-card">
<h2>Base de données sujets</h2>
<div style="overflow-x: auto;">
<table class="modern-table">
<thead>
<tr>
<th>UID</th>
<th>Sujet</th>
<th>Accréditation</th>
<th style="text-align: right;">Opérations</th>
</tr>
</thead>
<tbody>
<?php if (empty($tbl_auth_all)): ?>
<tr>
<td colspan="4" class="empty-state">Aucun sujet détecté dans la base.</td>
</tr>
<?php else:
?><?php foreach ($tbl_auth_all as $tbl_auth):
?><?php
$cl_auth_id = (int) $tbl_auth['cl_auth_id'];
$cl_auth_user = (string) $tbl_auth['cl_auth_user'];
$cl_auth_right = (string) $tbl_auth['cl_auth_right'];
?>
<tr>
<td><span class="user-id">#<?php echo sprintf('%03d', $cl_auth_id); ?></span></td>
<td><strong><?php echo htmlspecialchars($cl_auth_user, ENT_QUOTES, 'UTF-8'); ?></strong></td>
<td>
<span class="badge <?php echo $cl_auth_right === 'admin' ? 'badge-admin' : 'badge-member'; ?>">
<?php echo $cl_auth_right; ?>
</span>
</td>
<td>
<div class="row-actions" style="justify-content: flex-end;">
<a class="btn-modern btn-mini" href="admin.php?edit=<?php echo $cl_auth_id; ?>">Editer</a>
<form method="post" action="admin.php" onsubmit="return confirm('Terminer ce sujet ? Cette action est irréversible.');">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="admin_action" value="delete">
<input type="hidden" name="cl_auth_id" value="<?php echo $cl_auth_id; ?>">
<button class="btn-modern btn-mini danger" type="submit">X</button>
</form>
</div>
</td>
</tr>
<?php endforeach;
?><?php endif; ?>
</tbody>
</table>
</div>
</section>
</main>
</div>
</body>
</html>

View File

@ -1,3 +1,4 @@
[hidden] { display: none !important; }
@font-face {
font-family: 'Electrolize';
src: url('../fonts/Electrolize-Regular.ttf') format('truetype');
@ -309,4 +310,49 @@ a:hover {
.connexion-bouton:hover {
background-color: rgb(0 0 0 / 20%);
cursor: pointer;
}
}
/* Auth / admin helpers */
.connexion-div-menu {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.connexion-div-menu #accountLabel {
display: inline-block;
}
.connexion-div-menu.is-authenticated {
cursor: default;
}
.connexion-actions[hidden] { display: none !important; }
.connexion-actions {
display: inline-flex;
align-items: center;
gap: 10px;
}
.connexion-actions a {
color: #f4e3b2;
text-decoration: none;
border-bottom: 1px solid rgba(244, 227, 178, 0.35);
}
.connexion-actions a:hover {
color: #ffffff;
border-bottom-color: rgba(255, 255, 255, 0.8);
}
.login-status {
min-height: 18px;
}
.login-status.is-error {
color: #ff8080;
}
.login-status.is-success {
color: #9fe29f;
}

111
db/auth.php Normal file
View File

@ -0,0 +1,111 @@
<?php
require_once __DIR__ . '/config.php';
function auth_start_session(): void
{
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
}
function auth_bootstrap(): void
{
static $auth_bootstrap_done = false;
if ($auth_bootstrap_done) {
return;
}
$pdo = db();
$pdo->exec(
"CREATE TABLE IF NOT EXISTS tbl_auth (
cl_auth_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
cl_auth_user VARCHAR(190) NOT NULL UNIQUE,
cl_auth_pass VARCHAR(255) NOT NULL,
cl_auth_right ENUM('admin', 'member') NOT NULL DEFAULT 'member'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
$sql_count_admin = "SELECT COUNT(*) FROM tbl_auth WHERE cl_auth_right = 'admin'";
$stmt_count_admin = $pdo->query($sql_count_admin);
$cl_auth_admin_total = (int) $stmt_count_admin->fetchColumn();
if ($cl_auth_admin_total === 0) {
[$cl_auth_user, $plain_default_password] = auth_default_admin_credentials();
$cl_auth_pass = password_hash($plain_default_password, PASSWORD_DEFAULT);
$cl_auth_right = 'admin';
$stmt_insert_admin = $pdo->prepare(
'INSERT INTO tbl_auth (cl_auth_user, cl_auth_pass, cl_auth_right) VALUES (:cl_auth_user, :cl_auth_pass, :cl_auth_right)'
);
$stmt_insert_admin->execute([
'cl_auth_user' => $cl_auth_user,
'cl_auth_pass' => $cl_auth_pass,
'cl_auth_right' => $cl_auth_right,
]);
}
$auth_bootstrap_done = true;
}
function auth_default_admin_credentials(): array
{
return ['admin', 'ReactAdmin!2026'];
}
function auth_csrf_token(): string
{
auth_start_session();
if (empty($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function auth_validate_csrf(?string $csrf_token): bool
{
auth_start_session();
if (!isset($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) {
return false;
}
if ($csrf_token === null) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $csrf_token);
}
function auth_is_admin(): bool
{
auth_start_session();
return isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
}
function auth_flash_set(string $flash_type, string $flash_message): void
{
auth_start_session();
$_SESSION['flash'] = [
'type' => $flash_type,
'message' => $flash_message,
];
}
function auth_flash_get(): ?array
{
auth_start_session();
if (!isset($_SESSION['flash']) || !is_array($_SESSION['flash'])) {
return null;
}
$flash = $_SESSION['flash'];
unset($_SESSION['flash']);
return $flash;
}

17
db/config.php Normal file
View File

@ -0,0 +1,17 @@
<?php
// Generated by setup_mariadb_project.sh — edit as needed.
define('DB_HOST', '127.0.0.1');
define('DB_NAME', 'app_39514');
define('DB_USER', 'app_39514');
define('DB_PASS', 'ee6da88c-09af-4b48-b728-7a55edfb4e42');
function db() {
static $pdo;
if (!$pdo) {
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
}
return $pdo;
}

View File

@ -1,172 +1,190 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="BlackOps Agency, Star Citizen, Advocacy, UEE, Sécurité, Exploration, Renseignement, Patrouilles, Chasses de primes, Investigation, Neutralisation de menaces, Assistance humanitaire, Abordages, Réseaux anti-toxicité, Formation tactique, Entraînements, Coopération, Zones UEE, Systèmes inexplorés, Lore Star Citizen, Missions multi-joueurs, Protocole Arkange, Protocole Vigilance, Respect des lois impériales, Justice interstellaire, Criminalité galactique, Piraterie, XenoThreat, Vanduul, Sécurité spatiale, Leadership BlackOps, Collaboration stratégique, Combat spatial, Star Citizen gameplay, Exploration stratégique, Missions BlackOps Agency, Réputation UEE, Gestion des conflits, BlackOps, Agency, Recrutement Star Citizen, Zones de guerre, Star Citizen immersion.">
<title>Rapid Emergency & Action Combat Team / Star Citizen</title>
<link rel="stylesheet" type="text/css" href="css/styles.css">
<link rel="stylesheet" type="text/css" href="css/default.css" />
<link rel="stylesheet" type="text/css" href="css/component.css" />
<link rel="stylesheet" type="text/css" href="css/switch.css" />
<link rel="stylesheet" type="text/css" href="css/styles-modal.css" />
<script src="js/modernizr.custom.js"></script>
</head>
<body>
<div class="connexion-div-menu md-trigger" data-modal="modal-Login"><a href="#">Connexion</a></div>
<div class="se-switch switch-center" role="group" aria-label="Choix entre FR et EN">
<a class="se-switch__btn" href="index.php" data-site="se1">🇫🇷</a>
<a class="se-switch__btn is-active" href="index-en.php" data-site="se2" aria-current="page">🇬🇧</a>
</div>
<div class="md-modal md-effect-1" id="modal-Login">
<div class="md-content">
<h3>
<img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" />
LOGIN INTERFACE
<a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a>
</h3>
<div>
<p class="txt-center">Enter your credentials to access the secure area.*</p>
<form>
<div>
<p class="txt-center"><input class="connexion-champ" id="idr" type="text" name="username" placeholder="RID" /></p>
<p class="txt-center"><input class="connexion-champ" id="pwr" type="password" name="password" placeholder="PASSWORD" /></p>
</div>
<div>
<p class="txt-center"><input class="connexion-bouton" type="submit" value="Authenticate" /></p>
</div>
</form>
<p class="txt-center txt-s10">* Access under high surveillance. By confirming your credentials, you agree to the R.E.A.C.T. Initiative security protocols. Any breach of protocol is subject to sanctions.</p>
</div>
</div>
</div>
<div class="md-modal md-effect-1" id="modal-About">
<div class="md-content">
<h3>
<img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" />
MORE INFORMATIONS
<a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a>
</h3>
<div>
<img class="float-left" src="img/fl_security.png" width="300" height="460" alt="" />
<p>La sécurité et la protection des intérêts impériaux sont des priorités absolues pour lagence.</p>
<p>En tant que garante de lordre dans les zones sous juridiction de lUEE, lagence BOPS s'engage à prévenir, neutraliser et répondre efficacement à toutes les formes de menaces, qu'elles soient internes ou externes aux territoires de l'Empire.</p>
<p>Cette mission repose sur une approche combinant vigilance, réactivité et expertise tactique, afin dassurer la stabilité, la sécurité des citoyens et le respect des lois impériales.</p>
<p>Chaque opération est conçue pour inspirer confiance aux populations locales tout en projetant une image de force et de discipline au sein de lunivers connu.</p>
<p class="ul-style">
Surveillance active des secteurs critiques pour dissuader les menaces potentielles.</br>
Protection rapprochée de convois, personnalités importantes ou cargaisons sensibles.</br>
Neutralisation de vaisseaux hostiles ou suspects, avec récupération dactifs stratégiques.</br>
Ciblage et capture ou élimination de fugitifs dangereux, conformément aux lois impériales.</br>
Protection des civils en détresse et aide aux infrastructures endommagées.</br>
Collecte et analyse dinformations sur des activités suspectes, incidents ou organisations hostiles.
</p>
</div>
</div>
</div>
<div class="center-page-bops"></div>
<div class="center-div-menu">
<div class="padding50">
<p class="txt-center txt-bold txt-s40 small-caps padding25 txt-or">Rapid Emergency & Action Combat Team</p>
<!-- <p class="txt-center txt-bold txt-italic txt-s20 padding15 txt-or"><span class="padding5">SÉCURITÉ</span> —— <span class="padding5">INTELLIGENCE</span> —— <span class="padding5">SAUVETAGE</span></p> -->
<p class="txt-justify padding5">Founded to combat extreme criminality across the Verse. R.E.A.C.T. is an independent operational label, uniting dedicated pilots from all horizons to neutralize threats where the law is failing.</p>
<p class="txt-center txt-bold txt-s22 padding25 txt-or">—————— R.E.A.C.T. is not an organization, but a tactical standard ——————</p>
<p class="txt-justify padding5">It is a cross-org initiative designed for players who share the same vision: protecting citizens and NPCs from hostile entities. Wearing the R.E.A.C.T. badge doesnt mean leaving your organization; it means joining a rapid response network.</p>
</div>
<div class="center-div-menu-flex txt-bold">
<div class="menu-item md-trigger" data-modal="modal-About"><a href="#">> MORE INFORMATIONS <</a></div>
</div>
</div>
<div class="black-filter"></div>
<!-- Vidéo de fond -->
<div class="video-container" aria-hidden="true">
<iframe
id="react-video"
src="https://www.youtube-nocookie.com/embed/tFDqWOqm1G8?controls=0&autoplay=1&mute=1&playsinline=1&loop=1&playlist=tFDqWOqm1G8&cc_load_policy=0&iv_load_policy=3&modestbranding=1&rel=0&enablejsapi=1"
title="Présentation R.E.A.C.T — YouTube"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen"
allowfullscreen
referrerpolicy="strict-origin-when-cross-origin">
</iframe>
</div>
<div class="md-overlay"></div><!-- the overlay element -->
<!-- classie.js by @desandro: https://github.com/desandro/classie -->
<script src="js/classie.js"></script>
<script src="js/modalEffects.js"></script>
<!-- for the blur effect -->
<!-- by @derSchepp https://github.com/Schepp/CSS-Filters-Polyfill -->
<script>
// this is important for IEs
var polyfilter_scriptpath = '/js/';
</script>
<script src="js/cssParser.js"></script>
<script src="js/css-filters-polyfill.js"></script>
<script>
// On ajoute dynamiquement l'origine à l'URL de l'iframe (recommandé par l'API YouTube)
(function attachOrigin(){
const iframe = document.getElementById('react-video');
const url = new URL(iframe.src);
if (!url.searchParams.has('origin')) {
url.searchParams.set('origin', window.location.origin);
iframe.src = url.toString();
}
})();
// Utilitaires pour parler au lecteur via postMessage (API YouTube)
function ytCommand(func, args = []) {
const iframe = document.getElementById('react-video');
if (!iframe || !iframe.contentWindow) return;
iframe.contentWindow.postMessage(JSON.stringify({ event: 'command', func, args }), '*');
}
// Au premier geste utilisateur : on remet le son et on met le volume à 50 %
function enableSoundOnce() {
ytCommand('unMute');
ytCommand('setVolume', [50]);
// Masquer le bouton
const btn = document.getElementById('unmute-btn');
if (btn) btn.style.display = 'none';
// Retirer les écouteurs pour ne pas répéter laction
window.removeEventListener('pointerdown', enableSoundOnce);
window.removeEventListener('keydown', enableSoundOnce);
window.removeEventListener('touchstart', enableSoundOnce, { passive: true });
}
// Bouton dédié
// document.getElementById('unmute-btn').addEventListener('click', enableSoundOnce);
// …ou nimporte quel premier geste sur la page
//window.addEventListener('pointerdown', enableSoundOnce, { once: true });
//window.addEventListener('keydown', enableSoundOnce, { once: true });
//window.addEventListener('touchstart', enableSoundOnce, { once: true, passive: true });
</script>
<div class="footer txt-s12 txt-center">
<p>
<a href="https://robertsspaceindustries.com/en/orgs/REACT" target="_blank" title="Notre page RSI"><img src="img/icon09b.png" width="48" height="48" alt="" /></a>
<a href="https://discord.gg/dAvST8E7Mq" target="_blank" title="Notre Discord"><img src="img/icon08b.png" width="48" height="48" alt="" /></a>
<a href="https://robertsspaceindustries.com" target="_blank" title="RSI"><img src="img/icon10.png" width="48" height="48" alt="" /></a>
</p>
<p>Copyright © 2025-<?php echo date('Y'); ?> | www.react-sc.fr | All Rights Reserved.</p>
</div>
</body>
</html>
<?php
require_once __DIR__ . '/db/auth.php';
auth_start_session();
auth_bootstrap();
$session_cl_auth_user = isset($_SESSION['user']) ? (string) $_SESSION['user'] : '';
$session_cl_auth_right = isset($_SESSION['role']) ? (string) $_SESSION['role'] : '';
$is_authenticated = $session_cl_auth_user !== '';
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="BlackOps Agency, Star Citizen, Advocacy, UEE, Sécurité, Exploration, Renseignement, Patrouilles, Chasses de primes, Investigation, Neutralisation de menaces, Assistance humanitaire, Abordages, Réseaux anti-toxicité, Formation tactique, Entraînements, Coopération, Zones UEE, Systèmes inexplorés, Lore Star Citizen, Missions multi-joueurs, Protocole Arkange, Protocole Vigilance, Respect des lois impériales, Justice interstellaire, Criminalité galactique, Piraterie, XenoThreat, Vanduul, Sécurité spatiale, Leadership BlackOps, Collaboration stratégique, Combat spatial, Star Citizen gameplay, Exploration stratégique, Missions BlackOps Agency, Réputation UEE, Gestion des conflits, BlackOps, Agency, Recrutement Star Citizen, Zones de guerre, Star Citizen immersion.">
<title>Rapid Emergency & Action Combat Team / Star Citizen</title>
<link rel="stylesheet" type="text/css" href="css/styles.css">
<link rel="stylesheet" type="text/css" href="css/default.css" />
<link rel="stylesheet" type="text/css" href="css/component.css" />
<link rel="stylesheet" type="text/css" href="css/switch.css" />
<link rel="stylesheet" type="text/css" href="css/styles-modal.css" />
<script src="js/modernizr.custom.js"></script>
</head>
<body>
<div class="connexion-div-menu <?php echo $is_authenticated ? 'is-authenticated' : 'md-trigger'; ?>" data-login-label="Login" <?php echo $is_authenticated ? '' : 'data-modal="modal-Login"'; ?> id="accountPanel">
<span id="accountLabel"><?php echo htmlspecialchars($is_authenticated ? $session_cl_auth_user : 'Login', ENT_QUOTES, 'UTF-8'); ?></span>
<span class="connexion-actions" id="accountActions" <?php echo $is_authenticated ? '' : 'hidden'; ?>>
<a id="adminLink" href="admin.php" <?php echo $session_cl_auth_right === 'admin' ? '' : 'hidden'; ?>>Admin</a>
<a id="logoutLink" href="logout.php">Déconnexion</a>
</span>
</div>
<div class="se-switch switch-center" role="group" aria-label="Choix entre FR et EN">
<a class="se-switch__btn" href="index.php" data-site="se1">🇫🇷</a>
<a class="se-switch__btn is-active" href="index-en.php" data-site="se2" aria-current="page">🇬🇧</a>
</div>
<div class="md-modal md-effect-1" id="modal-Login">
<div class="md-content">
<h3>
<img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" />
LOGIN INTERFACE
<a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a>
</h3>
<div>
<p class="txt-center">Enter your credentials to access the secure area.*</p>
<form class="js-login-form" method="post" action="login.php">
<div>
<p class="txt-center"><input class="connexion-champ" id="idr" type="text" name="cl_auth_user" placeholder="RID" required /></p>
<p class="txt-center"><input class="connexion-champ" id="pwr" type="password" name="cl_auth_pass" placeholder="PASSWORD" required /></p>
</div>
<div>
<p class="txt-center"><input class="connexion-bouton" type="submit" value="Authenticate" /></p>
<p class="txt-center login-status" id="loginStatus" aria-live="polite"></p>
</div>
</form>
<p class="txt-center txt-s10">* Access under high surveillance. By confirming your credentials, you agree to the R.E.A.C.T. Initiative security protocols. Any breach of protocol is subject to sanctions.</p>
</div>
</div>
</div>
<div class="md-modal md-effect-1" id="modal-About">
<div class="md-content">
<h3>
<img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" />
MORE INFORMATIONS
<a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a>
</h3>
<div>
<img class="float-left" src="img/fl_security.png" width="300" height="460" alt="" />
<p>La sécurité et la protection des intérêts impériaux sont des priorités absolues pour lagence.</p>
<p>En tant que garante de lordre dans les zones sous juridiction de lUEE, lagence BOPS s'engage à prévenir, neutraliser et répondre efficacement à toutes les formes de menaces, qu'elles soient internes ou externes aux territoires de l'Empire.</p>
<p>Cette mission repose sur une approche combinant vigilance, réactivité et expertise tactique, afin dassurer la stabilité, la sécurité des citoyens et le respect des lois impériales.</p>
<p>Chaque opération est conçue pour inspirer confiance aux populations locales tout en projetant une image de force et de discipline au sein de lunivers connu.</p>
<p class="ul-style">
Surveillance active des secteurs critiques pour dissuader les menaces potentielles.</br>
Protection rapprochée de convois, personnalités importantes ou cargaisons sensibles.</br>
Neutralisation de vaisseaux hostiles ou suspects, avec récupération dactifs stratégiques.</br>
Ciblage et capture ou élimination de fugitifs dangereux, conformément aux lois impériales.</br>
Protection des civils en détresse et aide aux infrastructures endommagées.</br>
Collecte et analyse dinformations sur des activités suspectes, incidents ou organisations hostiles.
</p>
</div>
</div>
</div>
<div class="center-page-bops"></div>
<div class="center-div-menu">
<div class="padding50">
<p class="txt-center txt-bold txt-s40 small-caps padding25 txt-or">Rapid Emergency & Action Combat Team</p>
<!-- <p class="txt-center txt-bold txt-italic txt-s20 padding15 txt-or"><span class="padding5">SÉCURITÉ</span> —— <span class="padding5">INTELLIGENCE</span> —— <span class="padding5">SAUVETAGE</span></p> -->
<p class="txt-justify padding5">Founded to combat extreme criminality across the Verse. R.E.A.C.T. is an independent operational label, uniting dedicated pilots from all horizons to neutralize threats where the law is failing.</p>
<p class="txt-center txt-bold txt-s22 padding25 txt-or">—————— R.E.A.C.T. is not an organization, but a tactical standard ——————</p>
<p class="txt-justify padding5">It is a cross-org initiative designed for players who share the same vision: protecting citizens and NPCs from hostile entities. Wearing the R.E.A.C.T. badge doesnt mean leaving your organization; it means joining a rapid response network.</p>
</div>
<div class="center-div-menu-flex txt-bold">
<div class="menu-item md-trigger" data-modal="modal-About"><a href="#">> MORE INFORMATIONS <</a></div>
</div>
</div>
<div class="black-filter"></div>
<!-- Vidéo de fond -->
<div class="video-container" aria-hidden="true">
<iframe
id="react-video"
src="https://www.youtube-nocookie.com/embed/tFDqWOqm1G8?controls=0&autoplay=1&mute=1&playsinline=1&loop=1&playlist=tFDqWOqm1G8&cc_load_policy=0&iv_load_policy=3&modestbranding=1&rel=0&enablejsapi=1"
title="Présentation R.E.A.C.T — YouTube"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen"
allowfullscreen
referrerpolicy="strict-origin-when-cross-origin">
</iframe>
</div>
<div class="md-overlay"></div><!-- the overlay element -->
<!-- classie.js by @desandro: https://github.com/desandro/classie -->
<script src="js/classie.js"></script>
<script src="js/modalEffects.js"></script>
<script src="js/auth.js"></script>
<!-- for the blur effect -->
<!-- by @derSchepp https://github.com/Schepp/CSS-Filters-Polyfill -->
<script>
// this is important for IEs
var polyfilter_scriptpath = '/js/';
</script>
<script src="js/cssParser.js"></script>
<script src="js/css-filters-polyfill.js"></script>
<script>
// On ajoute dynamiquement l'origine à l'URL de l'iframe (recommandé par l'API YouTube)
(function attachOrigin(){
const iframe = document.getElementById('react-video');
const url = new URL(iframe.src);
if (!url.searchParams.has('origin')) {
url.searchParams.set('origin', window.location.origin);
iframe.src = url.toString();
}
})();
// Utilitaires pour parler au lecteur via postMessage (API YouTube)
function ytCommand(func, args = []) {
const iframe = document.getElementById('react-video');
if (!iframe || !iframe.contentWindow) return;
iframe.contentWindow.postMessage(JSON.stringify({ event: 'command', func, args }), '*');
}
// Au premier geste utilisateur : on remet le son et on met le volume à 50 %
function enableSoundOnce() {
ytCommand('unMute');
ytCommand('setVolume', [50]);
// Masquer le bouton
const btn = document.getElementById('unmute-btn');
if (btn) btn.style.display = 'none';
// Retirer les écouteurs pour ne pas répéter laction
window.removeEventListener('pointerdown', enableSoundOnce);
window.removeEventListener('keydown', enableSoundOnce);
window.removeEventListener('touchstart', enableSoundOnce, { passive: true });
}
// Bouton dédié
// document.getElementById('unmute-btn').addEventListener('click', enableSoundOnce);
// …ou nimporte quel premier geste sur la page
//window.addEventListener('pointerdown', enableSoundOnce, { once: true });
//window.addEventListener('keydown', enableSoundOnce, { once: true });
//window.addEventListener('touchstart', enableSoundOnce, { once: true, passive: true });
</script>
<div class="footer txt-s12 txt-center">
<p>
<a href="https://robertsspaceindustries.com/en/orgs/REACT" target="_blank" title="Notre page RSI"><img src="img/icon09b.png" width="48" height="48" alt="" /></a>
<a href="https://discord.gg/dAvST8E7Mq" target="_blank" title="Notre Discord"><img src="img/icon08b.png" width="48" height="48" alt="" /></a>
<a href="https://robertsspaceindustries.com" target="_blank" title="RSI"><img src="img/icon10.png" width="48" height="48" alt="" /></a>
</p>
<p>Copyright © 2025-<?php echo date('Y'); ?> | www.react-sc.fr | All Rights Reserved.</p>
</div>
</body>
</html>

370
index.php
View File

@ -1,176 +1,194 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="BlackOps Agency, Star Citizen, Advocacy, UEE, Sécurité, Exploration, Renseignement, Patrouilles, Chasses de primes, Investigation, Neutralisation de menaces, Assistance humanitaire, Abordages, Réseaux anti-toxicité, Formation tactique, Entraînements, Coopération, Zones UEE, Systèmes inexplorés, Lore Star Citizen, Missions multi-joueurs, Protocole Arkange, Protocole Vigilance, Respect des lois impériales, Justice interstellaire, Criminalité galactique, Piraterie, XenoThreat, Vanduul, Sécurité spatiale, Leadership BlackOps, Collaboration stratégique, Combat spatial, Star Citizen gameplay, Exploration stratégique, Missions BlackOps Agency, Réputation UEE, Gestion des conflits, BlackOps, Agency, Recrutement Star Citizen, Zones de guerre, Star Citizen immersion.">
<title>Rapid Emergency & Action Combat Team / Star Citizen</title>
<link rel="stylesheet" type="text/css" href="css/styles.css">
<link rel="stylesheet" type="text/css" href="css/default.css" />
<link rel="stylesheet" type="text/css" href="css/component.css" />
<link rel="stylesheet" type="text/css" href="css/switch.css" />
<link rel="stylesheet" type="text/css" href="css/styles-modal.css" />
<script src="js/modernizr.custom.js"></script>
</head>
<body>
<div class="assets-div-menu">
<a href="#">Contenu à venir</a>
</div>
<div class="connexion-div-menu md-trigger" data-modal="modal-Login"><a href="#">Connexion</a></div>
<div class="se-switch switch-center" role="group" aria-label="Choix entre FR et EN">
<a class="se-switch__btn is-active" href="index.php" data-site="se1" aria-current="page">🇫🇷</a>
<a class="se-switch__btn" href="index-en.php" data-site="se2">🇬🇧</a>
</div>
<div class="md-modal md-effect-1" id="modal-Login">
<div class="md-content">
<h3>
<img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" />
INTERFACE DE CONNEXION
<a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a>
</h3>
<div>
<p class="txt-center">Entrer vos identifiants pour acceder à l'espace sécurisé.*</p>
<form>
<div>
<p class="txt-center"><input class="connexion-champ" id="idr" type="text" name="username" placeholder="RID" /></p>
<p class="txt-center"><input class="connexion-champ" id="pwr" type="password" name="password" placeholder="MOT DE PASSE" /></p>
</div>
<div>
<p class="txt-center"><input class="connexion-bouton" type="submit" value="S'authentifier" /></p>
</div>
</form>
<p class="txt-center txt-s10">* Accès sous haute surveillance. En confirmant vos identifiants, vous acceptez les protocoles de sécurité de l'initiative R.E.A.C.T. Toute infraction aux protocoles est passible de sanctions.</p>
</div>
</div>
</div>
<div class="md-modal md-effect-1" id="modal-About">
<div class="md-content">
<h3>
<img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" />
EN SAVOIR PLUS
<a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a>
</h3>
<div>
<img class="float-left" src="img/fl_security.png" width="300" height="460" alt="" />
<p>La sécurité et la protection des intérêts impériaux sont des priorités absolues pour lagence.</p>
<p>En tant que garante de lordre dans les zones sous juridiction de lUEE, lagence BOPS s'engage à prévenir, neutraliser et répondre efficacement à toutes les formes de menaces, qu'elles soient internes ou externes aux territoires de l'Empire.</p>
<p>Cette mission repose sur une approche combinant vigilance, réactivité et expertise tactique, afin dassurer la stabilité, la sécurité des citoyens et le respect des lois impériales.</p>
<p>Chaque opération est conçue pour inspirer confiance aux populations locales tout en projetant une image de force et de discipline au sein de lunivers connu.</p>
<p class="ul-style">
Surveillance active des secteurs critiques pour dissuader les menaces potentielles.</br>
Protection rapprochée de convois, personnalités importantes ou cargaisons sensibles.</br>
Neutralisation de vaisseaux hostiles ou suspects, avec récupération dactifs stratégiques.</br>
Ciblage et capture ou élimination de fugitifs dangereux, conformément aux lois impériales.</br>
Protection des civils en détresse et aide aux infrastructures endommagées.</br>
Collecte et analyse dinformations sur des activités suspectes, incidents ou organisations hostiles.
</p>
</div>
</div>
</div>
<div class="center-page-bops"></div>
<div class="center-div-menu">
<div class="padding50">
<p class="txt-center txt-bold txt-s40 small-caps padding25 txt-or">Rapid Emergency & Action Combat Team</p>
<!-- <p class="txt-center txt-bold txt-italic txt-s20 padding15 txt-or"><span class="padding5">SÉCURITÉ</span> —— <span class="padding5">INTELLIGENCE</span> —— <span class="padding5">SAUVETAGE</span></p> -->
<p class="txt-justify padding5">Fondée pour combattre la criminalité extrême à travers lUnivers, la R.E.A.C.T. est un label opérationnel indépendant. Nous unissons des pilotes dévoués de tous horizons pour neutraliser les menaces la loi fait défaut.</p>
<p class="txt-center txt-bold txt-s22 padding25 txt-or">—————— La R.E.A.C.T. nest pas une organisation, mais un standard tactique ——————</p>
<p class="txt-justify padding5">Cest une initiative inter-organisations conçue pour les joueurs qui partagent la même vision : protéger les citoyens et les PNJs des entités hostiles. Porter linsigne R.E.A.C.T. ne signifie pas quitter votre organisation ; cela signifie rejoindre un réseau d'intervention rapide.</p>
</div>
<div class="center-div-menu-flex txt-bold">
<div class="menu-item md-trigger" data-modal="modal-About"><a href="#">> EN SAVOIR PLUS <</a></div>
</div>
</div>
<div class="black-filter"></div>
<!-- Vidéo de fond -->
<div class="video-container" aria-hidden="true">
<iframe
id="react-video"
src="https://www.youtube-nocookie.com/embed/tFDqWOqm1G8?controls=0&autoplay=1&mute=1&playsinline=1&loop=1&playlist=tFDqWOqm1G8&cc_load_policy=0&iv_load_policy=3&modestbranding=1&rel=0&enablejsapi=1"
title="Présentation R.E.A.C.T — YouTube"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen"
allowfullscreen
referrerpolicy="strict-origin-when-cross-origin">
</iframe>
</div>
<div class="md-overlay"></div><!-- the overlay element -->
<!-- classie.js by @desandro: https://github.com/desandro/classie -->
<script src="js/classie.js"></script>
<script src="js/modalEffects.js"></script>
<!-- for the blur effect -->
<!-- by @derSchepp https://github.com/Schepp/CSS-Filters-Polyfill -->
<script>
// this is important for IEs
var polyfilter_scriptpath = '/js/';
</script>
<script src="js/cssParser.js"></script>
<script src="js/css-filters-polyfill.js"></script>
<script>
// On ajoute dynamiquement l'origine à l'URL de l'iframe (recommandé par l'API YouTube)
(function attachOrigin(){
const iframe = document.getElementById('react-video');
const url = new URL(iframe.src);
if (!url.searchParams.has('origin')) {
url.searchParams.set('origin', window.location.origin);
iframe.src = url.toString();
}
})();
// Utilitaires pour parler au lecteur via postMessage (API YouTube)
function ytCommand(func, args = []) {
const iframe = document.getElementById('react-video');
if (!iframe || !iframe.contentWindow) return;
iframe.contentWindow.postMessage(JSON.stringify({ event: 'command', func, args }), '*');
}
// Au premier geste utilisateur : on remet le son et on met le volume à 50 %
function enableSoundOnce() {
ytCommand('unMute');
ytCommand('setVolume', [50]);
// Masquer le bouton
const btn = document.getElementById('unmute-btn');
if (btn) btn.style.display = 'none';
// Retirer les écouteurs pour ne pas répéter laction
window.removeEventListener('pointerdown', enableSoundOnce);
window.removeEventListener('keydown', enableSoundOnce);
window.removeEventListener('touchstart', enableSoundOnce, { passive: true });
}
// Bouton dédié
// document.getElementById('unmute-btn').addEventListener('click', enableSoundOnce);
// …ou nimporte quel premier geste sur la page
//window.addEventListener('pointerdown', enableSoundOnce, { once: true });
//window.addEventListener('keydown', enableSoundOnce, { once: true });
//window.addEventListener('touchstart', enableSoundOnce, { once: true, passive: true });
</script>
<div class="footer txt-s12 txt-center">
<p>
<a href="https://robertsspaceindustries.com/en/orgs/REACT" target="_blank" title="Notre page RSI"><img src="img/icon09b.png" width="48" height="48" alt="" /></a>
<a href="https://discord.gg/dAvST8E7Mq" target="_blank" title="Notre Discord"><img src="img/icon08b.png" width="48" height="48" alt="" /></a>
<a href="https://robertsspaceindustries.com" target="_blank" title="RSI"><img src="img/icon10.png" width="48" height="48" alt="" /></a>
</p>
<p>Copyright © 2025-<?php echo date('Y'); ?> | www.react-sc.fr | Tous droits réservés.</p>
</div>
</body>
</html>
<?php
require_once __DIR__ . '/db/auth.php';
auth_start_session();
auth_bootstrap();
$session_cl_auth_user = isset($_SESSION['user']) ? (string) $_SESSION['user'] : '';
$session_cl_auth_right = isset($_SESSION['role']) ? (string) $_SESSION['role'] : '';
$is_authenticated = $session_cl_auth_user !== '';
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="BlackOps Agency, Star Citizen, Advocacy, UEE, Sécurité, Exploration, Renseignement, Patrouilles, Chasses de primes, Investigation, Neutralisation de menaces, Assistance humanitaire, Abordages, Réseaux anti-toxicité, Formation tactique, Entraînements, Coopération, Zones UEE, Systèmes inexplorés, Lore Star Citizen, Missions multi-joueurs, Protocole Arkange, Protocole Vigilance, Respect des lois impériales, Justice interstellaire, Criminalité galactique, Piraterie, XenoThreat, Vanduul, Sécurité spatiale, Leadership BlackOps, Collaboration stratégique, Combat spatial, Star Citizen gameplay, Exploration stratégique, Missions BlackOps Agency, Réputation UEE, Gestion des conflits, BlackOps, Agency, Recrutement Star Citizen, Zones de guerre, Star Citizen immersion.">
<title>Rapid Emergency & Action Combat Team / Star Citizen</title>
<link rel="stylesheet" type="text/css" href="css/styles.css">
<link rel="stylesheet" type="text/css" href="css/default.css" />
<link rel="stylesheet" type="text/css" href="css/component.css" />
<link rel="stylesheet" type="text/css" href="css/switch.css" />
<link rel="stylesheet" type="text/css" href="css/styles-modal.css" />
<script src="js/modernizr.custom.js"></script>
</head>
<body>
<div class="assets-div-menu">
<a href="#">Contenu à venir</a>
</div>
<div class="connexion-div-menu <?php echo $is_authenticated ? 'is-authenticated' : 'md-trigger'; ?>" data-login-label="Connexion" <?php echo $is_authenticated ? '' : 'data-modal="modal-Login"'; ?> id="accountPanel">
<span id="accountLabel"><?php echo htmlspecialchars($is_authenticated ? $session_cl_auth_user : 'Connexion', ENT_QUOTES, 'UTF-8'); ?></span>
<span class="connexion-actions" id="accountActions" <?php echo $is_authenticated ? '' : 'hidden'; ?>>
<a id="adminLink" href="admin.php" <?php echo $session_cl_auth_right === 'admin' ? '' : 'hidden'; ?>>Admin</a>
<a id="logoutLink" href="logout.php">Déconnexion</a>
</span>
</div>
<div class="se-switch switch-center" role="group" aria-label="Choix entre FR et EN">
<a class="se-switch__btn is-active" href="index.php" data-site="se1" aria-current="page">🇫🇷</a>
<a class="se-switch__btn" href="index-en.php" data-site="se2">🇬🇧</a>
</div>
<div class="md-modal md-effect-1" id="modal-Login">
<div class="md-content">
<h3>
<img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" />
INTERFACE DE CONNEXION
<a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a>
</h3>
<div>
<p class="txt-center">Entrer vos identifiants pour acceder à l'espace sécurisé.*</p>
<form class="js-login-form" method="post" action="login.php">
<div>
<p class="txt-center"><input class="connexion-champ" id="idr" type="text" name="cl_auth_user" placeholder="RID" required /></p>
<p class="txt-center"><input class="connexion-champ" id="pwr" type="password" name="cl_auth_pass" placeholder="MOT DE PASSE" required /></p>
</div>
<div>
<p class="txt-center"><input class="connexion-bouton" type="submit" value="S'authentifier" /></p>
<p class="txt-center login-status" id="loginStatus" aria-live="polite"></p>
</div>
</form>
<p class="txt-center txt-s10">* Accès sous haute surveillance. En confirmant vos identifiants, vous acceptez les protocoles de sécurité de l'initiative R.E.A.C.T. Toute infraction aux protocoles est passible de sanctions.</p>
</div>
</div>
</div>
<div class="md-modal md-effect-1" id="modal-About">
<div class="md-content">
<h3>
<img class="float-left" src="img/icon_bops.png" width="48" height="48" alt="" />
EN SAVOIR PLUS
<a class="frame-icon-close md-close float-right" href="#"><img src="img/icon_close.png" width="48" height="48" alt="" /></a>
</h3>
<div>
<img class="float-left" src="img/fl_security.png" width="300" height="460" alt="" />
<p>La sécurité et la protection des intérêts impériaux sont des priorités absolues pour lagence.</p>
<p>En tant que garante de lordre dans les zones sous juridiction de lUEE, lagence BOPS s'engage à prévenir, neutraliser et répondre efficacement à toutes les formes de menaces, qu'elles soient internes ou externes aux territoires de l'Empire.</p>
<p>Cette mission repose sur une approche combinant vigilance, réactivité et expertise tactique, afin dassurer la stabilité, la sécurité des citoyens et le respect des lois impériales.</p>
<p>Chaque opération est conçue pour inspirer confiance aux populations locales tout en projetant une image de force et de discipline au sein de lunivers connu.</p>
<p class="ul-style">
Surveillance active des secteurs critiques pour dissuader les menaces potentielles.</br>
Protection rapprochée de convois, personnalités importantes ou cargaisons sensibles.</br>
Neutralisation de vaisseaux hostiles ou suspects, avec récupération dactifs stratégiques.</br>
Ciblage et capture ou élimination de fugitifs dangereux, conformément aux lois impériales.</br>
Protection des civils en détresse et aide aux infrastructures endommagées.</br>
Collecte et analyse dinformations sur des activités suspectes, incidents ou organisations hostiles.
</p>
</div>
</div>
</div>
<div class="center-page-bops"></div>
<div class="center-div-menu">
<div class="padding50">
<p class="txt-center txt-bold txt-s40 small-caps padding25 txt-or">Rapid Emergency & Action Combat Team</p>
<!-- <p class="txt-center txt-bold txt-italic txt-s20 padding15 txt-or"><span class="padding5">SÉCURITÉ</span> —— <span class="padding5">INTELLIGENCE</span> —— <span class="padding5">SAUVETAGE</span></p> -->
<p class="txt-justify padding5">Fondée pour combattre la criminalité extrême à travers lUnivers, la R.E.A.C.T. est un label opérationnel indépendant. Nous unissons des pilotes dévoués de tous horizons pour neutraliser les menaces la loi fait défaut.</p>
<p class="txt-center txt-bold txt-s22 padding25 txt-or">—————— La R.E.A.C.T. nest pas une organisation, mais un standard tactique ——————</p>
<p class="txt-justify padding5">Cest une initiative inter-organisations conçue pour les joueurs qui partagent la même vision : protéger les citoyens et les PNJs des entités hostiles. Porter linsigne R.E.A.C.T. ne signifie pas quitter votre organisation ; cela signifie rejoindre un réseau d'intervention rapide.</p>
</div>
<div class="center-div-menu-flex txt-bold">
<div class="menu-item md-trigger" data-modal="modal-About"><a href="#">> EN SAVOIR PLUS <</a></div>
</div>
</div>
<div class="black-filter"></div>
<!-- Vidéo de fond -->
<div class="video-container" aria-hidden="true">
<iframe
id="react-video"
src="https://www.youtube-nocookie.com/embed/tFDqWOqm1G8?controls=0&autoplay=1&mute=1&playsinline=1&loop=1&playlist=tFDqWOqm1G8&cc_load_policy=0&iv_load_policy=3&modestbranding=1&rel=0&enablejsapi=1"
title="Présentation R.E.A.C.T — YouTube"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; fullscreen"
allowfullscreen
referrerpolicy="strict-origin-when-cross-origin">
</iframe>
</div>
<div class="md-overlay"></div><!-- the overlay element -->
<!-- classie.js by @desandro: https://github.com/desandro/classie -->
<script src="js/classie.js"></script>
<script src="js/modalEffects.js"></script>
<script src="js/auth.js"></script>
<!-- for the blur effect -->
<!-- by @derSchepp https://github.com/Schepp/CSS-Filters-Polyfill -->
<script>
// this is important for IEs
var polyfilter_scriptpath = '/js/';
</script>
<script src="js/cssParser.js"></script>
<script src="js/css-filters-polyfill.js"></script>
<script>
// On ajoute dynamiquement l'origine à l'URL de l'iframe (recommandé par l'API YouTube)
(function attachOrigin(){
const iframe = document.getElementById('react-video');
const url = new URL(iframe.src);
if (!url.searchParams.has('origin')) {
url.searchParams.set('origin', window.location.origin);
iframe.src = url.toString();
}
})();
// Utilitaires pour parler au lecteur via postMessage (API YouTube)
function ytCommand(func, args = []) {
const iframe = document.getElementById('react-video');
if (!iframe || !iframe.contentWindow) return;
iframe.contentWindow.postMessage(JSON.stringify({ event: 'command', func, args }), '*');
}
// Au premier geste utilisateur : on remet le son et on met le volume à 50 %
function enableSoundOnce() {
ytCommand('unMute');
ytCommand('setVolume', [50]);
// Masquer le bouton
const btn = document.getElementById('unmute-btn');
if (btn) btn.style.display = 'none';
// Retirer les écouteurs pour ne pas répéter laction
window.removeEventListener('pointerdown', enableSoundOnce);
window.removeEventListener('keydown', enableSoundOnce);
window.removeEventListener('touchstart', enableSoundOnce, { passive: true });
}
// Bouton dédié
// document.getElementById('unmute-btn').addEventListener('click', enableSoundOnce);
// …ou nimporte quel premier geste sur la page
//window.addEventListener('pointerdown', enableSoundOnce, { once: true });
//window.addEventListener('keydown', enableSoundOnce, { once: true });
//window.addEventListener('touchstart', enableSoundOnce, { once: true, passive: true });
</script>
<div class="footer txt-s12 txt-center">
<p>
<a href="https://robertsspaceindustries.com/en/orgs/REACT" target="_blank" title="Notre page RSI"><img src="img/icon09b.png" width="48" height="48" alt="" /></a>
<a href="https://discord.gg/dAvST8E7Mq" target="_blank" title="Notre Discord"><img src="img/icon08b.png" width="48" height="48" alt="" /></a>
<a href="https://robertsspaceindustries.com" target="_blank" title="RSI"><img src="img/icon10.png" width="48" height="48" alt="" /></a>
</p>
<p>Copyright © 2025-<?php echo date('Y'); ?> | www.react-sc.fr | Tous droits réservés.</p>
</div>
</body>
</html>

122
js/auth.js Normal file
View File

@ -0,0 +1,122 @@
(function () {
var loginForm = document.querySelector('.js-login-form');
var accountPanel = document.getElementById('accountPanel');
var accountLabel = document.getElementById('accountLabel');
var accountActions = document.getElementById('accountActions');
var adminLink = document.getElementById('adminLink');
var logoutLink = document.getElementById('logoutLink');
var loginStatus = document.getElementById('loginStatus');
var loginModal = document.getElementById('modal-Login');
var overlay = document.querySelector('.md-overlay');
if (!loginForm || !accountPanel || !accountLabel || !accountActions || !logoutLink) {
return;
}
function setStatus(message, isError) {
if (!loginStatus) {
return;
}
loginStatus.textContent = message || '';
loginStatus.classList.toggle('is-error', !!isError);
loginStatus.classList.toggle('is-success', !isError && !!message);
}
function closeModal() {
if (loginModal) {
loginModal.classList.remove('md-show');
}
if (document.documentElement) {
document.documentElement.classList.remove('md-perspective');
}
if (overlay) {
overlay.removeEventListener('click', closeModal);
}
}
function renderAuthenticated(user, role, adminUrl, logoutUrl) {
accountPanel.classList.add('is-authenticated');
accountPanel.classList.remove('md-trigger');
accountPanel.removeAttribute('data-modal');
accountLabel.textContent = user;
accountActions.hidden = false;
if (role === 'admin' && adminUrl) {
adminLink.href = adminUrl;
adminLink.hidden = false;
} else {
adminLink.hidden = true;
}
if (logoutUrl) {
logoutLink.href = logoutUrl;
}
}
function renderLoggedOut(defaultLabel) {
accountPanel.classList.remove('is-authenticated');
accountPanel.classList.add('md-trigger');
accountPanel.setAttribute('data-modal', 'modal-Login');
accountLabel.textContent = defaultLabel;
accountActions.hidden = true;
adminLink.hidden = true;
}
loginForm.addEventListener('submit', function (event) {
event.preventDefault();
setStatus('', false);
var formData = new FormData(loginForm);
fetch('login.php', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
})
.then(function (response) {
return response.json().then(function (payload) {
return {
ok: response.ok,
payload: payload
};
});
})
.then(function (result) {
if (!result.ok || !result.payload.success) {
throw new Error(result.payload.message || 'Connexion impossible.');
}
renderAuthenticated(result.payload.user, result.payload.role, result.payload.adminUrl, result.payload.logoutUrl);
setStatus(result.payload.message || 'Connexion réussie.', false);
loginForm.reset();
window.setTimeout(closeModal, 250);
})
.catch(function (error) {
setStatus(error.message || 'Connexion impossible.', true);
});
});
logoutLink.addEventListener('click', function (event) {
event.preventDefault();
fetch(logoutLink.href, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(function (response) {
return response.json();
})
.then(function () {
renderLoggedOut(accountPanel.dataset.loginLabel || 'Connexion');
setStatus('', false);
})
.catch(function () {
window.location.href = logoutLink.href;
});
});
})();

89
login.php Normal file
View File

@ -0,0 +1,89 @@
<?php
require_once __DIR__ . '/db/auth.php';
auth_start_session();
auth_bootstrap();
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode([
'success' => false,
'message' => 'Méthode non autorisée.',
], JSON_UNESCAPED_UNICODE);
exit;
}
$submitted_cl_auth_user = trim((string) ($_POST['cl_auth_user'] ?? ''));
$submitted_cl_auth_pass = (string) ($_POST['cl_auth_pass'] ?? '');
if ($submitted_cl_auth_user === '' || $submitted_cl_auth_pass === '') {
http_response_code(422);
echo json_encode([
'success' => false,
'message' => 'Identifiants incomplets.',
], JSON_UNESCAPED_UNICODE);
exit;
}
$stmt_tbl_auth = db()->prepare(
'SELECT cl_auth_id, cl_auth_user, cl_auth_pass, cl_auth_right
FROM tbl_auth
WHERE cl_auth_user = :cl_auth_user
LIMIT 1'
);
$stmt_tbl_auth->execute([
'cl_auth_user' => $submitted_cl_auth_user,
]);
$tbl_auth = $stmt_tbl_auth->fetch();
if (!$tbl_auth) {
http_response_code(401);
echo json_encode([
'success' => false,
'message' => 'Identifiants invalides.',
], JSON_UNESCAPED_UNICODE);
exit;
}
$cl_auth_id = (int) $tbl_auth['cl_auth_id'];
$cl_auth_user = (string) $tbl_auth['cl_auth_user'];
$cl_auth_pass = (string) $tbl_auth['cl_auth_pass'];
$cl_auth_right = (string) $tbl_auth['cl_auth_right'];
unset($cl_auth_id);
if (!password_verify($submitted_cl_auth_pass, $cl_auth_pass)) {
http_response_code(401);
echo json_encode([
'success' => false,
'message' => 'Identifiants invalides.',
], JSON_UNESCAPED_UNICODE);
exit;
}
if (password_needs_rehash($cl_auth_pass, PASSWORD_DEFAULT)) {
$rehash_cl_auth_pass = password_hash($submitted_cl_auth_pass, PASSWORD_DEFAULT);
$stmt_update_password = db()->prepare(
'UPDATE tbl_auth SET cl_auth_pass = :cl_auth_pass WHERE cl_auth_user = :cl_auth_user'
);
$stmt_update_password->execute([
'cl_auth_pass' => $rehash_cl_auth_pass,
'cl_auth_user' => $cl_auth_user,
]);
}
session_regenerate_id(true);
$_SESSION['user'] = $cl_auth_user;
$_SESSION['role'] = $cl_auth_right;
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
echo json_encode([
'success' => true,
'message' => 'Connexion réussie.',
'user' => $cl_auth_user,
'role' => $cl_auth_right,
'adminUrl' => 'admin.php',
'logoutUrl' => 'logout.php',
], JSON_UNESCAPED_UNICODE);

36
logout.php Normal file
View File

@ -0,0 +1,36 @@
<?php
require_once __DIR__ . '/db/auth.php';
auth_start_session();
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$session_cookie_params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$session_cookie_params['path'],
$session_cookie_params['domain'],
$session_cookie_params['secure'],
$session_cookie_params['httponly']
);
}
session_destroy();
$is_ajax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
if ($is_ajax) {
header('Content-Type: application/json; charset=UTF-8');
echo json_encode([
'success' => true,
'message' => 'Déconnexion effectuée.',
], JSON_UNESCAPED_UNICODE);
exit;
}
header('Location: index.php');
exit;