clientRepository = $clientRepository; } /** * Xác thực khách hàng và tạo access + refresh token */ public function authenticateClient(array $credentials): array { if (!$token = Auth::guard('client')->attempt($credentials)) { throw ValidationException::withMessages([ 'email' => ['Thông tin đăng nhập không chính xác.'], ]); } $client = Auth::guard('client')->user(); if ((int)$client->status !== 1) { $statusLabel = match ((int)$client->status) { 0 => 'PENDING_VERIFY', 2 => 'tạm ngưng hoạt động', 3 => 'bị khoá', default => 'vô hiệu hoá', }; if ($statusLabel === 'PENDING_VERIFY') { throw ValidationException::withMessages([ 'email' => ['PENDING_VERIFY'], ]); } throw ValidationException::withMessages([ 'email' => ["Tài khoản của bạn đã {$statusLabel}, vui lòng liên hệ hotline."], ]); } $refreshToken = $this->createRefreshToken($client->id); return [ 'client' => $client, 'token' => $token, 'refresh_token' => $refreshToken, ]; } /** * Đăng ký khách hàng mới — status=0 (chờ xác thực OTP), chưa cấp token. */ public function registerClient(array $data): array { $code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT); $client = $this->clientRepository->create([ 'fullname' => $data['fullname'], 'phone' => $data['phone'], 'email' => $data['email'], 'password' => bcrypt($data['password']), 'status' => 0, 'email_verify_code' => $code, 'email_verify_code_expired_at' => now()->addMinutes(5), ]); $this->sendVerifyEmail($client, $code); if (!empty($data['trial_config'])) { $this->notifyAdminTrialRegistration($client, $data['trial_config']); } return ['client' => $client]; } /** * Báo admin (email + Telegram) khi khách hàng đăng ký dùng thử Cloud VPS. */ protected function notifyAdminTrialRegistration($client, array $trialConfig): void { try { $setting = Setting::where('key', 'setting')->first(); $settingValue = $setting ? json_decode((string) $setting->value, true) : []; $adminEmail = $settingValue['admin_email'] ?? null; if ($adminEmail) { Mail::to($adminEmail)->send(new TrialRegistrationRequestMail([ 'company_name' => $settingValue['company'] ?? config('app.name', 'Portal Admin'), 'customer_name' => $client->fullname, 'customer_email' => $client->email, 'customer_phone' => $client->phone, 'cpu' => $trialConfig['cpu'] ?? null, 'ram' => $trialConfig['ram'] ?? null, 'ssd' => $trialConfig['ssd'] ?? null, 'os' => $trialConfig['os'] ?? null, 'other' => $trialConfig['other'] ?? null, 'created_at' => now()->format('d/m/Y H:i:s'), ])); } else { Log::warning('Trial registration admin email not sent: admin_email not configured.', [ 'customer_id' => $client->id, ]); } } catch (\Throwable $e) { Log::warning('Send trial registration admin email failed: ' . $e->getMessage(), [ 'customer_id' => $client->id, ]); } (new Telegram())->SendTrialRegistration($client, $trialConfig); } /** * Xác thực OTP — kích hoạt tài khoản, trả về token. */ public function verifyEmail(string $email, string $code): array { $client = \App\Models\Customer::whereRaw('LOWER(email) = ?', [strtolower(trim($email))])->first(); if (! $client || $client->email_verify_code !== $code) { throw ValidationException::withMessages([ 'code' => ['Mã xác thực không chính xác.'], ]); } if (! $client->email_verify_code_expired_at || now()->gt($client->email_verify_code_expired_at)) { throw ValidationException::withMessages([ 'code' => ['Mã xác thực đã hết hạn, vui lòng yêu cầu gửi lại.'], ]); } $client->status = 1; $client->email_verify_code = null; $client->email_verify_code_expired_at = null; $client->email_verified_at = now(); $client->save(); $token = JWTAuth::fromUser($client); $refreshToken = $this->createRefreshToken($client->id); $this->sendWelcomeEmail($client); return [ 'client' => $client, 'token' => $token, 'refresh_token' => $refreshToken, ]; } /** * Gửi lại OTP xác thực (rate limit bởi caller nếu cần). */ public function resendVerifyCode(string $email): void { $client = \App\Models\Customer::whereRaw('LOWER(email) = ?', [strtolower(trim($email))])->first(); if (! $client || $client->status !== 0) { return; } // Nếu mã cũ còn hạn thì không gửi lại — phải đợi hết hạn if ($client->email_verify_code_expired_at && now()->lt($client->email_verify_code_expired_at)) { throw ValidationException::withMessages([ 'email' => ['Mã xác thực vẫn còn hiệu lực, vui lòng kiểm tra email của bạn.'], ]); } $code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT); $client->email_verify_code = $code; $client->email_verify_code_expired_at = now()->addMinutes(5); $client->save(); $this->sendVerifyEmail($client, $code); } protected function sendVerifyEmail($client, string $code): void { try { Mail::to($client->email)->later(now()->addSeconds(5), new VerifyAccountMail([ 'fullname' => $client->fullname, 'email' => $client->email, 'verify_code' => $code, ])); } catch (\Throwable $e) { Log::warning('Send verify email failed: ' . $e->getMessage(), [ 'customer_id' => $client->id, 'email' => $client->email, ]); } } /** * Gửi email chào mừng sau khi xác thực tài khoản thành công. */ protected function sendWelcomeEmail($client): void { try { Mail::to($client->email)->send(new WelcomeMail([ 'fullname' => $client->fullname, 'email' => $client->email, ])); } catch (\Throwable $e) { Log::warning('Send welcome email failed: ' . $e->getMessage(), [ 'customer_id' => $client->id, 'email' => $client->email, ]); } } /** * Tạo refresh token mới, lưu hash vào DB, trả về raw token. */ public function createRefreshToken(int $customerId): string { $raw = Str::random(64); $hash = hash('sha256', $raw); RefreshToken::create([ 'customer_id' => $customerId, 'token' => $hash, 'expires_at' => now()->addDays(self::REFRESH_TOKEN_TTL_DAYS), ]); return $raw; } /** * Xác minh refresh token, xoay vòng (rotation), trả về access token mới + refresh token mới. * Ném ValidationException nếu token không hợp lệ hoặc đã hết hạn. */ public function rotateRefreshToken(string $rawToken): array { $hash = hash('sha256', $rawToken); $record = RefreshToken::where('token', $hash)->first(); if (!$record || $record->isExpired()) { // Xoá token cũ nếu tồn tại nhưng hết hạn $record?->delete(); throw ValidationException::withMessages([ 'refresh_token' => ['Refresh token không hợp lệ hoặc đã hết hạn.'], ]); } $customer = $record->customer; if (!$customer || (int)$customer->status !== 1) { $record->delete(); throw ValidationException::withMessages([ 'refresh_token' => ['Tài khoản không hợp lệ.'], ]); } // Rotation: xoá token cũ $record->delete(); // Tạo access token mới $newAccessToken = JWTAuth::fromUser($customer); // Tạo refresh token mới $newRefreshToken = $this->createRefreshToken($customer->id); return [ 'client' => $customer, 'token' => $newAccessToken, 'refresh_token' => $newRefreshToken, ]; } /** * Huỷ refresh token khi đăng xuất. */ public function revokeRefreshToken(string $rawToken): void { $hash = hash('sha256', $rawToken); RefreshToken::where('token', $hash)->delete(); } }