View file system/api.php

File size: 4.5Kb
<?php
/**
 * CMS: LaiCMS (v1.0 Edition 2026)
 * File: system/api.php
 * Description: Защищенный JSON API шлюз с глубокой инспекцией трафика.
 */

declare(strict_types=1);

// 1. Усиленные заголовки безопасности (Security Headers)
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY'); // Защита от Clickjacking
header('Content-Security-Policy: default-src \'none\';'); // API не должен ничего грузить стороннего

require_once 'db.php';
require_once 'functions.php';

// Объект ответа (инициализация)
$response = [
    'status'  => 'error',
    'message' => 'Critical error',
    'payload' => []
];

/**
 * 2. Rate Limiting (Защита от DOS/Bruteforce)
 * Ограничиваем количество запросов к API (например, не более 2 в секунду)
 */
if (isset($_SESSION['last_api_request']) && (microtime(true) - $_SESSION['last_api_request'] < 0.5)) {
    http_response_code(429);
    echo json_encode(['status' => 'error', 'message' => 'Too Many Requests']);
    exit;
}
$_SESSION['last_api_request'] = microtime(true);

// 3. Валидация входного потока
$inputJSON = file_get_contents('php://input');
$input = json_decode($inputJSON, true);

if (json_last_error() !== JSON_ERROR_NONE) {
    http_response_code(400);
    echo json_encode(['status' => 'error', 'message' => 'Malformed JSON']);
    exit;
}

// 4. Глубокая проверка авторизации и CSRF
if (empty($_SESSION['user_id'])) {
    http_response_code(401);
    echo json_encode(['status' => 'error', 'message' => 'Unauthorized']);
    exit;
}

if (!isset($input['csrf_token']) || !check_csrf((string)$input['csrf_token'])) {
    http_response_code(403);
    echo json_encode(['status' => 'error', 'message' => 'Security Token Mismatch']);
    exit;
}

$action = (string)($input['action'] ?? '');



try {
    switch ($action) {
        
        case 'get_user_info':
            // Используем наше защищенное ядро q()
            $res = q("SELECT id, username, role, balance FROM users WHERE id = ?", [(int)$_SESSION['user_id']]);
            $user = $res->fetch_assoc();
            
            if (!$user) throw new Exception("User not found");

            $response['status'] = 'success';
            $response['payload'] = $user;
            break;

        case 'update_user_role':
            if (!isAdmin()) throw new Exception("Access Denied: Admin privileges required");
            
            $u_id = (int)($input['user_id'] ?? 0);
            $new_role = ($input['role'] === 'admin') ? 'admin' : 'user';
            
            if ($u_id === (int)$_SESSION['user_id']) {
                throw new Exception("Self-role modification prohibited");
            }

            $success = q("UPDATE users SET role = ? WHERE id = ?", [$new_role, $u_id]);
            
            if ($success) {
                $response['status'] = 'success';
                $response['message'] = "Role for ID $u_id updated to $new_role";
                
                track_online_status(); // Обновляем инфо об админе
            } else {
                throw new Exception("Database update failed");
            }
            break;

        case 'get_system_stats':
            if (!isAdmin()) throw new Exception("Forbidden");
            
            $res = q("SELECT COUNT(id) as total FROM users");
            $total = $res->fetch_assoc()['total'] ?? 0;

            $response['status'] = 'success';
            $response['payload'] = [
                'total_users' => (int)$total,
                'php_version' => PHP_VERSION,
                'load_average' => function_exists('sys_getloadavg') ? sys_getloadavg()[0] : 'N/A'
            ];
            break;

        default:
            http_response_code(404);
            throw new Exception("Action '$action' is not registered");
    }
} catch (Throwable $e) {
    // В режиме 2026 мы логируем реальную ошибку в файл, а пользователю отдаем только безопасное сообщение
    error_log("[API Error] " . $e->getMessage() . " in " . $e->getFile());
    $response['message'] = $e->getMessage();
}

// 5. Очистка буфера и вывод
if (ob_get_length()) ob_clean();
echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);