getMessage() : 'An internal error occurred.'; echo json_encode(['success' => false, 'message' => $safe, 'errors' => []]); exit; }); $_ver = file_get_contents(NOVACPX_ROOT . '/VERSION') ?: file_get_contents('/opt/novacpx-src/VERSION') ?: '1.0.0'; header('X-NovaCPX-Version: ' . trim($_ver)); // CORS — only allow same-host origins on the panel ports or the known proxy hostnames $origin = $_SERVER['HTTP_ORIGIN'] ?? ''; $_allowedHosts = ['novacpx.orbishosting.com', 'admin.novacpx.orbishosting.com', 'reseller.novacpx.orbishosting.com', 'panel.novacpx.orbishosting.com', 'web.orbishosting.com']; $_originHost = parse_url($origin, PHP_URL_HOST) ?? ''; $_originPort = (int)(parse_url($origin, PHP_URL_PORT) ?? 0); $_panelPorts = [8880, 8881, 8882, 8883]; // hardcoded — Core.php not loaded yet if ($origin && ( in_array($_originHost, $_allowedHosts, true) || (in_array($_originPort, $_panelPorts, true) && filter_var($_originHost, FILTER_VALIDATE_IP)) )) { header("Access-Control-Allow-Origin: $origin"); header('Access-Control-Allow-Credentials: true'); header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With'); } if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; } require_once NOVACPX_LIB . '/Core.php'; require_once NOVACPX_LIB . '/Auth.php'; require_once NOVACPX_LIB . '/DB.php'; require_once NOVACPX_LIB . '/Response.php'; // Parse route: /api/endpoint/action $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $parts = array_values(array_filter(explode('/', $uri))); $apiIdx = array_search('api', $parts); $endpoint = $parts[$apiIdx + 1] ?? null; $action = $parts[$apiIdx + 2] ?? null; if (!$endpoint) { Response::json(['status' => 'ok', 'panel' => 'NovaCPX', 'version' => NOVACPX_VERSION]); } // Public endpoints (no auth required) $public = ['auth']; if (!in_array($endpoint, $public)) { $auth = Auth::getInstance(); if (!$auth->check()) { Response::error('Unauthorized', 401); } $currentUser = $auth->user(); } // Route to endpoint handler $endpointFile = NOVACPX_API . "/endpoints/{$endpoint}.php"; if (!file_exists($endpointFile)) { Response::error("Unknown endpoint: $endpoint", 404); } /** * Verify the current user can access a given account_id. * Returns the account row or sends a 404 error response. * Resellers may only access their own customers; users may only access their own account. */ function assert_account_access(int $accountId): array { global $currentUser; $db = DB::getInstance(); $acct = $db->fetchOne("SELECT a.*, u.reseller_id FROM accounts a JOIN users u ON u.id = a.user_id WHERE a.id = ?", [$accountId]); if (!$acct) Response::error("Account not found", 404); if ($currentUser['role'] === 'reseller' && (int)$acct['reseller_id'] !== $currentUser['uid']) { Response::error("Account not found", 404); } if ($currentUser['role'] === 'user') { $own = $db->fetchOne("SELECT id FROM accounts WHERE id = ? AND user_id = ?", [$accountId, $currentUser['uid']]); if (!$own) Response::error("Account not found", 404); } return $acct; } require $endpointFile;