39514-vm/db/auth.php
Flatlogic Bot d7e2a86f09 V1.4.6
2026-05-06 22:51:03 +00:00

511 lines
16 KiB
PHP

<?php
require_once __DIR__ . '/config.php';
function auth_config_value(array $environment_keys, array $constant_keys = []): ?string
{
foreach ($environment_keys as $environment_key) {
$value = getenv($environment_key);
if ($value !== false) {
$value = trim((string) $value);
if ($value !== '') {
return $value;
}
}
}
foreach ($constant_keys as $constant_key) {
if (defined($constant_key)) {
$value = trim((string) constant($constant_key));
if ($value !== '') {
return $value;
}
}
}
return null;
}
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', '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 <<<HTML
<div style="position:fixed;top:10px;right:10px;z-index:9999;background:rgba(10,14,18,0.94);border:1px solid rgba(162,155,120,0.45);border-radius:12px;padding:10px 12px;box-shadow:0 10px 24px rgba(0,0,0,0.35);backdrop-filter:blur(8px);font-family:Arial,sans-serif;color:#f2f2f2;min-width:220px;max-width:min(92vw,280px);">
<form method="post" action="{$action}" style="margin:0;display:flex;flex-direction:column;gap:8px;">
<input type="hidden" name="csrf_token" value="{$csrf}">
<input type="hidden" name="page_access_action" value="save">
<div style="font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#a29b78;">Accès page</div>
<div style="font-size:14px;font-weight:700;line-height:1.25;">{$label}</div>
<label style="display:flex;align-items:center;gap:8px;font-size:13px;opacity:.9;">
<input type="checkbox" checked disabled>
<span>Admin <small style="opacity:.7;">(toujours autorisé)</small></span>
</label>
<label style="display:flex;align-items:center;gap:8px;font-size:13px;">
<input type="checkbox" name="cl_allow_moderator" value="1" {$moderator_checked}>
<span>Modérateur</span>
</label>
<label style="display:flex;align-items:center;gap:8px;font-size:13px;">
<input type="checkbox" name="cl_allow_member" value="1" {$member_checked}>
<span>Membre</span>
</label>
<button type="submit" style="appearance:none;border:0;border-radius:8px;padding:8px 10px;background:#a29b78;color:#111;font-weight:700;cursor:pointer;">Appliquer</button>
</form>
</div>
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 = '<nav class="nav-tabs">';
foreach (auth_navigation_items() as $item) {
$file = (string) $item['file'];
$label = (string) $item['label'];
$admin_only = !empty($item['admin_only']);
if ($admin_only && !auth_is_admin()) {
continue;
}
if (!auth_is_admin() && !auth_user_can_access_page($file, $label)) {
continue;
}
$is_active = $file === $current_page ? ' class="active"' : '';
$html .= '<a href="' . htmlspecialchars($file, ENT_QUOTES, 'UTF-8') . '"' . $is_active . '>' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</a>';
}
$html .= '</nav>';
return $html;
}