diff --git a/app/Casts/EncryptedOrLegacy.php b/app/Casts/EncryptedOrLegacy.php new file mode 100644 index 0000000..5f99649 --- /dev/null +++ b/app/Casts/EncryptedOrLegacy.php @@ -0,0 +1,28 @@ +where('uuid', $uuid) + ->where('customer_id', $request->user()->id) + ->first(); + + $vm = $service?->vmware; + + if (!$vm) { + return response()->json(['error' => true, 'message' => 'Không tìm thấy dịch vụ'], 404); + } + + return response()->json(['error' => false, 'data' => ['password' => $vm->plain_password]]); + } + public function getConsoleTicket(Request $request, string $uuid, vCenter $serviceVCenter) { $customer = $request->user(); diff --git a/app/Http/Controllers/Api/Service/OrderControllerRefactor.php b/app/Http/Controllers/Api/Service/OrderControllerRefactor.php index e2f9899..0bc2764 100644 --- a/app/Http/Controllers/Api/Service/OrderControllerRefactor.php +++ b/app/Http/Controllers/Api/Service/OrderControllerRefactor.php @@ -361,10 +361,14 @@ class OrderControllerRefactor extends Controller ]); } - public function renewService(RenewServiceRequest $request, int $id): JsonResponse + public function renewService(RenewServiceRequest $request, string $id): JsonResponse { + if (! ctype_digit($id)) { + $id = (string) \App\Models\Service::where('uuid', $id)->where('customer_id', $request->user()->id)->value('id'); + } + $result = $this->orderQueryService->renewService( - $id, + (int) $id, $request->user(), (int) $request->validated()['months'], ); diff --git a/app/Http/Resources/Api/Client/Service/ServiceClientDetailResource.php b/app/Http/Resources/Api/Client/Service/ServiceClientDetailResource.php index 1c2ea68..9d4be25 100644 --- a/app/Http/Resources/Api/Client/Service/ServiceClientDetailResource.php +++ b/app/Http/Resources/Api/Client/Service/ServiceClientDetailResource.php @@ -10,6 +10,8 @@ use Illuminate\Support\Facades\Crypt; class ServiceClientDetailResource extends JsonResource { + public const MASKED_PASSWORD = '*****'; + /** Plesk panel login (persisted, reusable) — separate from the one-time FTP password, which is never stored. */ private function resolvePleskCredentials($hosting): ?array { @@ -60,7 +62,7 @@ class ServiceClientDetailResource extends JsonResource if ($addons->isNotEmpty()) { return [ 'cpu' => (int) $addons->where('addon_type', 'cpu')->sum('addon_value'), - 'ram' => (int) $addons->where('addon_type', 'ram')->sum('addon_value'), + 'ram' => round($addons->where('addon_type', 'ram')->sum('addon_value') / 1024, 2), 'disk' => (int) $addons->where('addon_type', 'disk')->sum('addon_value'), 'bandwidth' => (int) $addons->where('addon_type', 'bandwidth')->sum('addon_value'), ]; @@ -89,7 +91,6 @@ class ServiceClientDetailResource extends JsonResource $dnsCancelled = $dnsZone !== null && (int) $dnsZone->is_created === DnsDomain::STATUS_CANCELLED; return [ - 'id' => $this->id, 'uuid' => $this->uuid, 'type' => $this->type, 'server_name' => $this->server_name, @@ -115,12 +116,10 @@ class ServiceClientDetailResource extends JsonResource 'renewal_options' => \App\Support\RenewalPricing::options($product), ] : null, 'vm' => $vm ? [ - 'id' => $vm->id, - 'vmware_id' => $vm->vmware_id, 'ip_address' => $vm->ipAddress?->ip ?? $vm->ip, 'hostname' => $vm->hostname, 'username' => $vm->user, - 'password' => $vm->plain_password ?? null, + 'password' => $vm->plain_password ? self::MASKED_PASSWORD : null, 'port' => $vm->port, 'status' => $vm->status, 'ip' => $vm->ip ?? null, diff --git a/app/Http/Resources/Api/Service/VpsServiceResource.php b/app/Http/Resources/Api/Service/VpsServiceResource.php index c4ec58f..76b9273 100644 --- a/app/Http/Resources/Api/Service/VpsServiceResource.php +++ b/app/Http/Resources/Api/Service/VpsServiceResource.php @@ -14,7 +14,7 @@ class VpsServiceResource extends JsonResource if ($addons->isNotEmpty()) { return [ 'cpu' => (int) $addons->where('addon_type', 'cpu')->sum('addon_value'), - 'ram' => (int) $addons->where('addon_type', 'ram')->sum('addon_value'), + 'ram' => round($addons->where('addon_type', 'ram')->sum('addon_value') / 1024, 2), 'disk' => (int) $addons->where('addon_type', 'disk')->sum('addon_value'), 'bandwidth' => (int) $addons->where('addon_type', 'bandwidth')->sum('addon_value'), ]; diff --git a/app/Models/ModuleVmware.php b/app/Models/ModuleVmware.php index 791c0ad..e17ffa1 100644 --- a/app/Models/ModuleVmware.php +++ b/app/Models/ModuleVmware.php @@ -39,11 +39,8 @@ class ModuleVmware extends Model 'disk_gb', ]; - // NOTE: password must NOT use 'hashed' cast — it is a VM guest credential - // used in plaintext by PowerShell scripts (Invoke-VMScript) via SSH/PowerCLI. - // plain_password uses Laravel's 'encrypted' cast (APP_KEY, AES-256-CBC): stored - // encrypted at rest, transparently decrypted on read — no code elsewhere changes. protected $casts = [ + 'password' => \App\Casts\EncryptedOrLegacy::class, 'plain_password' => 'encrypted', ]; public function customer() diff --git a/routes/client.php b/routes/client.php index 7dbb454..c7cabcf 100644 --- a/routes/client.php +++ b/routes/client.php @@ -123,6 +123,7 @@ Route::prefix('v1') Route::get('web-design', 'indexWebDesign')->name('web-design.list'); Route::get('domain', 'indexDomain')->name('domain.list'); Route::get('{uuid}', 'getDetail')->name('detail'); + Route::get('{uuid}/vm-password', 'getVmPassword')->middleware('throttle:20,1')->name('vm-password'); Route::get('{uuid}/console-ticket', 'getConsoleTicket')->name('console-ticket'); Route::get('{uuid}/available-os', 'getAvailableOs')->name('available-os'); Route::post('{uuid}/rebuild', 'rebuild')->name('rebuild');