google2fa->generateSecretKey(); } public function qrUrl(User $user, string $secret): string { $company = config('app.name', 'HexaHost Panel'); return $this->google2fa->getQRCodeUrl($company, $user->email, $secret); } public function verify(User $user, string $code): bool { $secret = $this->decryptSecret($user); if (! $secret) { return false; } return $this->google2fa->verifyKey($secret, $code); } public function enable(User $user, string $secret, string $code): bool { if (! $this->google2fa->verifyKey($secret, $code)) { return false; } $recovery = collect(range(1, 8))->map(fn () => bin2hex(random_bytes(4)))->implode(','); $user->forceFill([ 'two_factor_secret' => Crypt::encryptString($secret), 'two_factor_recovery_codes' => Crypt::encryptString($recovery), 'two_factor_confirmed_at' => now(), ])->save(); return true; } public function disable(User $user): void { $user->forceFill([ 'two_factor_secret' => null, 'two_factor_recovery_codes' => null, 'two_factor_confirmed_at' => null, ])->save(); } public function isEnabled(User $user): bool { return $user->two_factor_confirmed_at !== null && $user->two_factor_secret !== null; } public function mustSetup(User $user): bool { if (! config('hosting.security.admin_2fa_required', true)) { return false; } return $user->isAdmin() && ! $this->isEnabled($user); } public function decryptSecret(User $user): ?string { if (! $user->two_factor_secret) { return null; } try { return Crypt::decryptString($user->two_factor_secret); } catch (\Throwable) { return null; } } }