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', 'moderator', 'member') NOT NULL DEFAULT 'member' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" ); $pdo->exec( "CREATE TABLE IF NOT EXISTS tbl_page_access ( cl_page_access_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, cl_page_key VARCHAR(190) NOT NULL UNIQUE, cl_page_file VARCHAR(190) NOT NULL UNIQUE, cl_page_label VARCHAR(190) NOT NULL, cl_allow_admin TINYINT(1) NOT NULL DEFAULT 1, cl_allow_moderator TINYINT(1) NOT NULL DEFAULT 0, cl_allow_member TINYINT(1) NOT NULL DEFAULT 0, cl_updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" ); $stmt_auth_role_column = $pdo->query("SHOW COLUMNS FROM tbl_auth LIKE 'cl_auth_right'"); $auth_role_column = $stmt_auth_role_column->fetch(); $auth_role_type = strtolower((string) ($auth_role_column['Type'] ?? '')); if (strpos($auth_role_type, "'moderator'") === false) { $pdo->exec("ALTER TABLE tbl_auth MODIFY cl_auth_right ENUM('admin', 'moderator', 'member') NOT NULL DEFAULT 'member'"); } $stmt_page_access_columns = $pdo->query('SHOW COLUMNS FROM tbl_page_access'); $page_access_columns = []; foreach ($stmt_page_access_columns->fetchAll() as $page_access_column) { $page_access_columns[] = (string) ($page_access_column['Field'] ?? ''); } if (!in_array('cl_allow_moderator', $page_access_columns, true)) { $pdo->exec('ALTER TABLE tbl_page_access ADD COLUMN cl_allow_moderator TINYINT(1) NOT NULL DEFAULT 0 AFTER cl_allow_admin'); $pdo->exec('UPDATE tbl_page_access SET cl_allow_moderator = cl_allow_member'); } $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(); if ($cl_auth_user === '' || $plain_default_password === '') { throw new RuntimeException( "Aucun administrateur n'existe et aucun couple DEFAULT_ADMIN_USER / DEFAULT_ADMIN_PASSWORD n'est configuré." ); } $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 { $cl_auth_user = auth_config_value( ['DEFAULT_ADMIN_USER', 'APP_DEFAULT_ADMIN_USER'], ['DEFAULT_ADMIN_USER', 'APP_DEFAULT_ADMIN_USER'] ) ?? 'admin'; $plain_default_password = auth_config_value( ['DEFAULT_ADMIN_PASSWORD', 'APP_DEFAULT_ADMIN_PASSWORD'], ['DEFAULT_ADMIN_PASSWORD', 'APP_DEFAULT_ADMIN_PASSWORD'] ); if ($plain_default_password === null) { return ['', '']; } return [$cl_auth_user, $plain_default_password]; } 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_logged_in(): bool { auth_start_session(); return isset($_SESSION['user']) && isset($_SESSION['role']); } function auth_is_admin(): bool { return auth_current_role() === 'admin'; } function auth_is_moderator(): bool { return auth_current_role() === 'moderator'; } function auth_valid_roles(): array { return ['admin', 'moderator', 'member']; } function auth_role_label(string $role): string { static $labels = [ 'admin' => 'Administrateur', 'moderator' => 'Modérateur', 'member' => 'Membre', ]; return $labels[$role] ?? ucfirst($role); } function auth_current_user(): string { auth_start_session(); return isset($_SESSION['user']) ? (string) $_SESSION['user'] : ''; } function auth_current_role(): string { auth_start_session(); return isset($_SESSION['role']) ? (string) $_SESSION['role'] : ''; } 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; } function auth_page_basename(string $page_file): string { $basename = basename(trim($page_file)); if ($basename === '' || preg_match('/^[a-zA-Z0-9._-]+$/', $basename) !== 1) { throw new InvalidArgumentException('Nom de page invalide.'); } return $basename; } function auth_page_default_member_access(string $page_file): int { static $member_defaults = [ 'scnotification.php' => 1, 'scpreset.php' => 1, ]; $page_file = auth_page_basename($page_file); return $member_defaults[$page_file] ?? 0; } function auth_page_default_moderator_access(string $page_file): int { return auth_page_default_member_access($page_file); } function auth_page_access_defaults(string $page_file, string $page_label = ''): array { $normalized_page_file = auth_page_basename($page_file); $normalized_page_label = trim($page_label) !== '' ? trim($page_label) : $normalized_page_file; return [ 'cl_page_key' => pathinfo($normalized_page_file, PATHINFO_FILENAME), 'cl_page_file' => $normalized_page_file, 'cl_page_label' => $normalized_page_label, 'cl_allow_admin' => 1, 'cl_allow_moderator' => auth_page_default_moderator_access($normalized_page_file), 'cl_allow_member' => auth_page_default_member_access($normalized_page_file), ]; } function auth_page_access_ensure(string $page_file, string $page_label = ''): array { auth_bootstrap(); $defaults = auth_page_access_defaults($page_file, $page_label); $pdo = db(); $stmt = $pdo->prepare( 'SELECT cl_page_access_id, cl_page_key, cl_page_file, cl_page_label, cl_allow_admin, cl_allow_moderator, cl_allow_member FROM tbl_page_access WHERE cl_page_file = :cl_page_file LIMIT 1' ); $stmt->execute([ 'cl_page_file' => $defaults['cl_page_file'], ]); $row = $stmt->fetch(); if (!$row) { $stmt_insert = $pdo->prepare( 'INSERT INTO tbl_page_access (cl_page_key, cl_page_file, cl_page_label, cl_allow_admin, cl_allow_moderator, cl_allow_member) VALUES (:cl_page_key, :cl_page_file, :cl_page_label, :cl_allow_admin, :cl_allow_moderator, :cl_allow_member)' ); $stmt_insert->execute($defaults); $stmt->execute([ 'cl_page_file' => $defaults['cl_page_file'], ]); $row = $stmt->fetch(); } elseif ($defaults['cl_page_label'] !== '' && (string) $row['cl_page_label'] !== $defaults['cl_page_label']) { $stmt_update_label = $pdo->prepare( 'UPDATE tbl_page_access SET cl_page_label = :cl_page_label WHERE cl_page_file = :cl_page_file' ); $stmt_update_label->execute([ 'cl_page_label' => $defaults['cl_page_label'], 'cl_page_file' => $defaults['cl_page_file'], ]); $row['cl_page_label'] = $defaults['cl_page_label']; } if (!$row) { throw new RuntimeException('Impossible d\'initialiser la configuration d\'accès de la page.'); } $row['cl_allow_admin'] = (int) ($row['cl_allow_admin'] ?? 1); $row['cl_allow_moderator'] = (int) ($row['cl_allow_moderator'] ?? 0); $row['cl_allow_member'] = (int) ($row['cl_allow_member'] ?? 0); return $row; } function auth_user_can_access_page(string $page_file, string $page_label = ''): bool { auth_start_session(); auth_bootstrap(); if (!auth_is_logged_in()) { return false; } $role = auth_current_role(); if ($role === 'admin') { return true; } $row = auth_page_access_ensure($page_file, $page_label); if ($role === 'moderator') { return (int) $row['cl_allow_moderator'] === 1; } if ($role === 'member') { return (int) $row['cl_allow_member'] === 1; } return false; } function auth_require_page_access(string $page_file, string $page_label = ''): void { if (!auth_is_logged_in()) { header('Location: index.php'); exit; } if (auth_is_admin()) { auth_page_access_ensure($page_file, $page_label); return; } if (auth_user_can_access_page($page_file, $page_label)) { return; } auth_flash_set('error', 'Accès refusé : cette page n\'est pas ouverte pour votre niveau d\'autorisation.'); header('Location: index.php'); exit; } function auth_handle_page_access_post(string $page_file, string $page_label = ''): void { auth_start_session(); auth_bootstrap(); if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') { return; } if (!isset($_POST['page_access_action'])) { return; } $redirect_target = auth_page_basename($page_file); if (!auth_is_admin()) { auth_flash_set('error', 'Seul un administrateur peut modifier les accès de page.'); header('Location: index.php'); exit; } $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: ' . $redirect_target); exit; } $row = auth_page_access_ensure($page_file, $page_label); $cl_allow_moderator = isset($_POST['cl_allow_moderator']) ? 1 : 0; $cl_allow_member = isset($_POST['cl_allow_member']) ? 1 : 0; $stmt = db()->prepare( 'UPDATE tbl_page_access SET cl_page_label = :cl_page_label, cl_allow_admin = 1, cl_allow_moderator = :cl_allow_moderator, cl_allow_member = :cl_allow_member WHERE cl_page_file = :cl_page_file' ); $stmt->execute([ 'cl_page_label' => $row['cl_page_label'], 'cl_allow_moderator' => $cl_allow_moderator, 'cl_allow_member' => $cl_allow_member, 'cl_page_file' => $row['cl_page_file'], ]); auth_flash_set('success', 'Accès mis à jour pour ' . $row['cl_page_label'] . '.'); header('Location: ' . $redirect_target); exit; } function auth_render_page_access_widget(string $page_file, string $page_label = ''): string { if (!auth_is_admin()) { return ''; } $row = auth_page_access_ensure($page_file, $page_label); $csrf_token = auth_csrf_token(); $action = htmlspecialchars($row['cl_page_file'], ENT_QUOTES, 'UTF-8'); $label = htmlspecialchars((string) $row['cl_page_label'], ENT_QUOTES, 'UTF-8'); $csrf = htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); $moderator_checked = (int) $row['cl_allow_moderator'] === 1 ? 'checked' : ''; $member_checked = (int) $row['cl_allow_member'] === 1 ? 'checked' : ''; return <<
Accès page
{$label}
HTML; } function auth_navigation_items(): array { return [ ['file' => 'admin.php', 'label' => 'Utilisateurs', 'admin_only' => true], ['file' => 'scwebhook.php', 'label' => 'WEBHOOK'], ['file' => 'scnotification.php', 'label' => 'NOTIF DISCORD'], ['file' => 'scitems.php', 'label' => 'Base d\'Objets'], ['file' => 'scstatsitem.php', 'label' => 'Stats Item'], ['file' => 'scitemcustom.php', 'label' => 'Objets perso.'], ['file' => 'sccharacters.php', 'label' => 'Personnages'], ['file' => 'scmanutention.php', 'label' => 'Manutention'], ['file' => 'scmining.php', 'label' => 'Scanner Minage'], ['file' => 'scmanufactures.php', 'label' => 'Manufactures'], ['file' => 'scvaisseaux.php', 'label' => 'Vaisseaux'], ['file' => 'scpreset.php', 'label' => 'Presets Vaisseau'], ]; } function auth_render_app_nav(string $current_page): string { if (!auth_is_logged_in()) { return ''; } $current_page = auth_page_basename($current_page); $html = ''; return $html; }