Enhance VirtFusion WHMCS module with security fixes, new features, and improved UX
Security improvements: - Enable SSL/TLS certificate verification by default (was disabled, MITM risk) - Remove error_reporting(0) that silenced all errors - Add input sanitization on all user parameters (int casting, regex filtering) - Return proper HTTP status codes (401, 403, 400, 500) instead of always 200 - Add XSS protection with htmlspecialchars and encodeURIComponent - Add null checks on API response data before property access New features: - Power management: boot, shutdown, restart, and force power off controls - Server rebuild: reinstall with any available OS template from client area - Server rename: change server display name via PATCH API - OS template fetching: client-side endpoint for rebuild OS selection - TestConnection: validate API credentials from WHMCS server settings - ServiceSingleSignOn: native WHMCS SSO integration for VirtFusion panel - Server status badge: visual indicator of server state in overview - Traffic usage display: show bandwidth used vs allocated - Checkout validation: ShoppingCartValidateCheckout hook ensures OS selection Ordering process improvements: - Add default "Select Operating System" placeholder option - Add "No SSH Key (Optional)" default for SSH dropdown - Hide SSH key field/container when no keys available - Wrap hook in try/catch to prevent checkout page breakage - Sanitize template names with htmlspecialchars - Use JSON_HEX_* flags for safe script injection Theme compatibility: - Properly formatted Smarty templates with readable indentation - Dual panel/card CSS classes for Bootstrap 3/4/5 compatibility - Responsive power button layout with mobile breakpoint - Framework-agnostic HTML that works with Six, Twenty-One, Lagom, and custom themes - Suspended service state messaging Code quality: - Readable, unminified JavaScript with JSDoc header - Structured CSS with logical section organization - Improved error messages throughout all provisioning functions - Added PATCH method support to Curl wrapper - Added curl error capture on connection failures - Added connection and request timeouts (10s/30s) - Fixed memory conversion to check key name instead of display name Documentation: - Complete README rewrite with installation, configuration, and troubleshooting guides - API endpoint reference table - Configurable options mapping documentation - Theme override instructions - Security considerations section https://claude.ai/code/session_01TCsJ4WZCGuEX3zqh1tQ2zx
This commit is contained in:
@@ -9,103 +9,173 @@ $vf = new Module();
|
||||
|
||||
$vf->isAuthenticated();
|
||||
|
||||
switch ($vf->validateAction(true)) {
|
||||
$action = $vf->validateAction(true);
|
||||
|
||||
switch ($action) {
|
||||
|
||||
/**
|
||||
*
|
||||
* Reset Password.
|
||||
*
|
||||
*/
|
||||
case 'resetPassword':
|
||||
|
||||
if ($vf->validateServiceID(true)) {
|
||||
|
||||
$client = $vf->validateUserOwnsService((int)$_GET['serviceID']);
|
||||
|
||||
if (!$client) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 200);
|
||||
}
|
||||
|
||||
$data = $vf->resetUserPassword((int)$_GET['serviceID'], $client);
|
||||
|
||||
if ($data) {
|
||||
$vf->output(['success' => true, 'data' => $data->data], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'error'], true, true, 200);
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
$client = $vf->validateUserOwnsService($serviceID);
|
||||
|
||||
if (!$client) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$data = $vf->resetUserPassword($serviceID, $client);
|
||||
|
||||
if ($data) {
|
||||
$vf->output(['success' => true, 'data' => $data->data], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Password reset failed'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
*
|
||||
* Get server information.
|
||||
*
|
||||
*/
|
||||
case 'serverData':
|
||||
|
||||
if ($vf->validateServiceID(true)) {
|
||||
|
||||
if (!$vf->validateUserOwnsService((int)$_GET['serviceID'])) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 200);
|
||||
}
|
||||
|
||||
$data = $vf->fetchServerData((int)$_GET['serviceID']);
|
||||
|
||||
if ($data) {
|
||||
|
||||
(new Module())->updateWhmcsServiceParamsOnServerObject((int)$_GET['serviceID'], $data);
|
||||
|
||||
$vf->output(['success' => true, 'data' => (new ServerResource())->process($data)], true, true, 200);
|
||||
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'error'], true, true, 200);
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$data = $vf->fetchServerData($serviceID);
|
||||
|
||||
if ($data) {
|
||||
(new Module())->updateWhmcsServiceParamsOnServerObject($serviceID, $data);
|
||||
$vf->output(['success' => true, 'data' => (new ServerResource())->process($data)], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to retrieve server data'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
*
|
||||
* Login as server owner.
|
||||
*
|
||||
*/
|
||||
case 'loginAsServerOwner':
|
||||
|
||||
if ($vf->validateServiceID(true)) {
|
||||
/**
|
||||
* A client can't log in as any user. Ownership should be validated.
|
||||
*/
|
||||
|
||||
if (!$vf->validateUserOwnsService((int)$_GET['serviceID'])) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 200);
|
||||
}
|
||||
|
||||
$token = $vf->fetchLoginTokens((int)$_GET['serviceID']);
|
||||
|
||||
if ($token) {
|
||||
|
||||
/**
|
||||
* A valid token/url was received.
|
||||
*/
|
||||
$vf->output(['success' => true, 'token_url' => $token], true, true, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Failed to get the token from the control panel or the service ID doesn't exist.
|
||||
*/
|
||||
$vf->output(['success' => false, 'errors' => 'token request error'], true, true, 200);
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$token = $vf->fetchLoginTokens($serviceID);
|
||||
|
||||
if ($token) {
|
||||
$vf->output(['success' => true, 'token_url' => $token], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to generate login token'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Power management actions: boot, shutdown, restart, poweroff
|
||||
*/
|
||||
case 'powerAction':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$powerAction = isset($_GET['powerAction']) ? preg_replace('/[^a-zA-Z]/', '', $_GET['powerAction']) : '';
|
||||
$allowedActions = ['boot', 'shutdown', 'restart', 'poweroff'];
|
||||
|
||||
if (!in_array($powerAction, $allowedActions, true)) {
|
||||
$vf->output(['success' => false, 'errors' => 'Invalid power action'], true, true, 400);
|
||||
}
|
||||
|
||||
$result = $vf->serverPowerAction($serviceID, $powerAction);
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['action' => $powerAction, 'message' => 'Power action queued successfully']], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Power action failed. The server may be locked or unavailable.'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Rebuild/reinstall server with new OS.
|
||||
*/
|
||||
case 'rebuild':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$osId = isset($_GET['osId']) ? (int) $_GET['osId'] : 0;
|
||||
$hostname = isset($_GET['hostname']) ? preg_replace('/[^a-zA-Z0-9.\-]/', '', $_GET['hostname']) : null;
|
||||
|
||||
if ($osId <= 0) {
|
||||
$vf->output(['success' => false, 'errors' => 'Invalid operating system ID'], true, true, 400);
|
||||
}
|
||||
|
||||
$result = $vf->rebuildServer($serviceID, $osId, $hostname);
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['message' => 'Server rebuild initiated successfully']], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Server rebuild failed. The server may be locked or unavailable.'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Rename server.
|
||||
*/
|
||||
case 'rename':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$newName = isset($_GET['name']) ? trim($_GET['name']) : '';
|
||||
$newName = htmlspecialchars($newName, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
if (empty($newName) || strlen($newName) > 255) {
|
||||
$vf->output(['success' => false, 'errors' => 'Invalid server name'], true, true, 400);
|
||||
}
|
||||
|
||||
$result = $vf->renameServer($serviceID, $newName);
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['message' => 'Server renamed successfully']], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Server rename failed'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Get available OS templates for rebuild.
|
||||
*/
|
||||
case 'osTemplates':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$templates = $vf->fetchOsTemplates($serviceID);
|
||||
|
||||
if ($templates !== false) {
|
||||
$vf->output(['success' => true, 'data' => $templates], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to fetch OS templates'], true, true, 500);
|
||||
break;
|
||||
|
||||
default:
|
||||
/**
|
||||
*
|
||||
* No valid action was specified.
|
||||
*
|
||||
*/
|
||||
$vf->output(['success' => false, 'errors' => 'invalid action'], true, true, 200);
|
||||
$vf->output(['success' => false, 'errors' => 'invalid action'], true, true, 400);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user