72 lines
1.8 KiB
PHP
72 lines
1.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\TwoFactorRecoveryCode;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Str;
|
|
use PragmaRX\Google2FA\Google2FA;
|
|
|
|
class TwoFactorService
|
|
{
|
|
public function __construct(private readonly Google2FA $google2fa) {}
|
|
|
|
public function generateSecret(): string
|
|
{
|
|
return $this->google2fa->generateSecretKey();
|
|
}
|
|
|
|
public function otpAuthUrl(User $user, string $secret): string
|
|
{
|
|
return $this->google2fa->getQRCodeUrl(
|
|
config('app.name'),
|
|
$user->email,
|
|
$secret,
|
|
);
|
|
}
|
|
|
|
public function verifyCode(string $secret, string $code): bool
|
|
{
|
|
return $this->google2fa->verifyKey($secret, $code, window: 1);
|
|
}
|
|
|
|
/**
|
|
* @return list<string> the plaintext recovery codes (shown once, never stored plaintext)
|
|
*/
|
|
public function generateRecoveryCodes(User $user): array
|
|
{
|
|
$user->twoFactorRecoveryCodes()->delete();
|
|
|
|
$plaintextCodes = [];
|
|
|
|
foreach (range(1, 10) as $_) {
|
|
$code = Str::random(10);
|
|
$plaintextCodes[] = $code;
|
|
|
|
TwoFactorRecoveryCode::create([
|
|
'user_id' => $user->id,
|
|
'code_hash' => Hash::make($code),
|
|
]);
|
|
}
|
|
|
|
return $plaintextCodes;
|
|
}
|
|
|
|
public function redeemRecoveryCode(User $user, string $code): bool
|
|
{
|
|
$recoveryCode = $user->twoFactorRecoveryCodes()
|
|
->whereNull('used_at')
|
|
->get()
|
|
->first(fn (TwoFactorRecoveryCode $recoveryCode) => Hash::check($code, $recoveryCode->code_hash));
|
|
|
|
if (! $recoveryCode) {
|
|
return false;
|
|
}
|
|
|
|
$recoveryCode->update(['used_at' => now()]);
|
|
|
|
return true;
|
|
}
|
|
}
|