mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
2ecf93a344
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.
103 lines
3.8 KiB
PHP
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;
|