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 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; } }