Files
myron 2ecf93a344 fix: hardcode panel ports in CORS check — PORT_USER etc undefined before Core.php loads
Using PORT_USER ?? 8880 threw Error in PHP 8 since the constant isn't defined
until Core.php is require_once'd later in the file. Every API request was
hitting the exception handler and returning 'An internal error occurred.',
breaking all logins and API calls.
2026-06-22 04:29:15 +00:00

103 lines
3.8 KiB
PHP

<?php
/**
* NovaCPX API Router
* All requests: /api/{endpoint}/{action}
*/
define('NOVACPX_ROOT', dirname(__DIR__));
define('NOVACPX_API', __DIR__);
define('NOVACPX_LIB', NOVACPX_ROOT . '/lib');
header('Content-Type: application/json');
// Global exception handler — prevents uncaught exceptions from crashing PHP-FPM (502)
set_exception_handler(function (Throwable $e) {
http_response_code(500);
// Never expose internal exception messages (may contain SQL, paths, credentials)
$safe = ($e instanceof \InvalidArgumentException || $e instanceof \RuntimeException)
? $e->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;