510 lines
16 KiB
PHP
510 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' => '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;
|
|
}
|