engine->generateSecretKey(); } /** * URI для QR-кода (otpauth://) — добавляется в приложение-аутентификатор. */ public function provisioningUri(User $user, string $secret): string { return $this->engine->getQRCodeUrl( (string) config('app.name'), (string) $user->email, $secret, ); } public function verify(string $secret, string $code): bool { // verifyKey возвращает позицию окна (int) при успехе либо false. return $this->engine->verifyKey($secret, $code) !== false; } /** * Сгенерировать набор одноразовых резервных кодов. * * @return array */ public function generateRecoveryCodes(): array { $count = (int) config('security.mfa.recovery_codes'); return Collection::times($count, fn () => Str::upper(Str::random(5).'-'.Str::random(5))) ->all(); } /** * @param array $codes * @return array */ public function hashRecoveryCodes(array $codes): array { return array_map(static fn (string $code) => Hash::make($code), $codes); } /** * Проверить и «погасить» резервный код. Возвращает новый список хешей или * null, если код не подошёл. * * @param array $hashedCodes * @return array|null */ public function consumeRecoveryCode(array $hashedCodes, string $input): ?array { foreach ($hashedCodes as $index => $hash) { if (Hash::check($input, $hash)) { unset($hashedCodes[$index]); return array_values($hashedCodes); } } return null; } }